From f29e35790e3bc11cb94bbd0750f0d7f63ff73134 Mon Sep 17 00:00:00 2001 From: Jacob Glueck Date: Mon, 25 Nov 2019 17:26:18 -0500 Subject: [PATCH 1/3] add axistream library --- apps/axistream/.gitignore | 3 + apps/axistream/Makefile | 56 ++ apps/axistream/README.md | 19 + apps/axistream/src/array.hh | 32 ++ apps/axistream/src/axi_stream_common.hh | 12 + apps/axistream/src/axi_stream_reader.hh | 168 ++++++ apps/axistream/src/axi_stream_writer.hh | 98 ++++ apps/axistream/src/hls_ap_axi_sdata.hh | 4 + apps/axistream/src/hls_int.hh | 4 + apps/axistream/src/hls_macros.hh | 8 + apps/axistream/src/hls_stream.hh | 4 + apps/axistream/src/message.hh | 498 ++++++++++++++++++ apps/axistream/src/util.hh | 29 + .../test/axi_stream_integeration_test.cc | 58 ++ apps/axistream/test/axi_stream_reader_test.cc | 101 ++++ apps/axistream/test/axi_stream_writer_test.cc | 133 +++++ apps/axistream/test/message_test.cc | 66 +++ apps/axistream/test/test_main.cc | 2 + apps/axistream/tl/tl.cc | 24 + apps/axistream/tl/tl.hh | 26 + apps/axistream/tl/tl_test.cc | 28 + 21 files changed, 1373 insertions(+) create mode 100644 apps/axistream/.gitignore create mode 100644 apps/axistream/Makefile create mode 100644 apps/axistream/README.md create mode 100644 apps/axistream/src/array.hh create mode 100644 apps/axistream/src/axi_stream_common.hh create mode 100644 apps/axistream/src/axi_stream_reader.hh create mode 100644 apps/axistream/src/axi_stream_writer.hh create mode 100644 apps/axistream/src/hls_ap_axi_sdata.hh create mode 100644 apps/axistream/src/hls_int.hh create mode 100644 apps/axistream/src/hls_macros.hh create mode 100644 apps/axistream/src/hls_stream.hh create mode 100644 apps/axistream/src/message.hh create mode 100644 apps/axistream/src/util.hh create mode 100644 apps/axistream/test/axi_stream_integeration_test.cc create mode 100644 apps/axistream/test/axi_stream_reader_test.cc create mode 100644 apps/axistream/test/axi_stream_writer_test.cc create mode 100644 apps/axistream/test/message_test.cc create mode 100644 apps/axistream/test/test_main.cc create mode 100644 apps/axistream/tl/tl.cc create mode 100644 apps/axistream/tl/tl.hh create mode 100644 apps/axistream/tl/tl_test.cc diff --git a/apps/axistream/.gitignore b/apps/axistream/.gitignore new file mode 100644 index 0000000..2fbeeb1 --- /dev/null +++ b/apps/axistream/.gitignore @@ -0,0 +1,3 @@ +build +bin +*.swp diff --git a/apps/axistream/Makefile b/apps/axistream/Makefile new file mode 100644 index 0000000..2eb6281 --- /dev/null +++ b/apps/axistream/Makefile @@ -0,0 +1,56 @@ +CXXFLAGS = -std=c++11 -Wall -Werror + +# Set these +CATCH_DIR = ../catch +HLS_INCLUDE_DIR = /u/tsqahfus/local/tools/xilinx/Vivado/2018.3/include/ + +SRC_DIR = src +TEST_DIR = test +TL_DIR = tl +BUILD_DIR = build +BIN_DIR = bin + +INCLUDE_DIRS = $(SRC_DIR) $(TEST_DIR) $(TL_DIR) $(CATCH_DIR) $(HLS_INCLUDE_DIR) +INCLUDE_FLAGS = $(addprefix -I,$(INCLUDE_DIRS)) + +SRCS = $(wildcard src/*.cc) +HDRS = $(wildcard src/*.hh) +OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(patsubst %.cc,%.o,$(SRCS)))) + +TEST_SRCS = $(wildcard test/*.cc) +TEST_HDRS = $(wildcard test/*.hh) +TEST_OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(patsubst %.cc,%.o,$(TEST_SRCS)))) + +TL_SRCS = $(wildcard tl/*.cc) +TL_HDRS = $(wildcard tl/*.hh) +TL_OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(patsubst %.cc,%.o,$(TL_SRCS)))) + +BINS = $(addprefix $(BIN_DIR)/,test_main tl_main) + +ARTIFACTS = $(OBJS) $(TEST_OBJS) $(TL_OBJS) $(BINS) + +all: $(OBJS) $(TEST_OBJS) $(BINS) + +$(BIN_DIR)/test_main: $(TEST_OBJS) + @mkdir -p $(BIN_DIR) + $(CXX) -o $@ $^ + +$(BIN_DIR)/tl_main: $(TL_OBJS) + @mkdir -p $(BIN_DIR) + $(CXX) -o $@ $^ + +$(BUILD_DIR)/%.o : $(SRC_DIR)/%.cc $(HDRS) + @mkdir -p $(BUILD_DIR) + $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCLUDE_FLAGS) + +$(BUILD_DIR)/%.o : $(TEST_DIR)/%.cc $(HDRS) + @mkdir -p $(BUILD_DIR) + $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCLUDE_FLAGS) + +$(BUILD_DIR)/%.o : $(TL_DIR)/%.cc $(HDRS) + @mkdir -p $(BUILD_DIR) + $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCLUDE_FLAGS) + +.PHONY: clean +clean: + rm -f $(ARTIFACTS) diff --git a/apps/axistream/README.md b/apps/axistream/README.md new file mode 100644 index 0000000..132ab7b --- /dev/null +++ b/apps/axistream/README.md @@ -0,0 +1,19 @@ +# Axistream + +## Contents +* `src`: the library itself +* `test`: unit tests +* `tl`: a simple top-level function for HLS synthesis + +## Building +The tests require [Catch2](https://github.com/catchorg/Catch2). +Download `catch.hpp`, and then set the `CATCH_DIR` variable in the `Makefile` +accordingly. + +Configure the following 2 lines the in Makefile: +``` +CATCH_DIR = ../catch +HLS_INCLUDE_DIR = /u/tsqahfus/local/tools/xilinx/Vivado/2018.3/include/ +``` + +Then run `make`. diff --git a/apps/axistream/src/array.hh b/apps/axistream/src/array.hh new file mode 100644 index 0000000..e275752 --- /dev/null +++ b/apps/axistream/src/array.hh @@ -0,0 +1,32 @@ +#pragma once +#include "hls_macros.hh" + +namespace util { + +/* + * Simple array type for use in HLS. + * This serves as a translatable version of std::array, + * for cases when an array with value semantics is needed. + * (As opposed to normal C arrays which decay to pointers.) + */ +template +struct array { + T data[N]; + + static int size() { + HLS_PRAGMA(HLS inline); + return N; + } + + const T& operator[](int i) const { + HLS_PRAGMA(HLS inline); + return data[i]; + } + + T& operator[](int i) { + HLS_PRAGMA(HLS inline); + return data[i]; + } +}; + +} diff --git a/apps/axistream/src/axi_stream_common.hh b/apps/axistream/src/axi_stream_common.hh new file mode 100644 index 0000000..c0cef5d --- /dev/null +++ b/apps/axistream/src/axi_stream_common.hh @@ -0,0 +1,12 @@ +#pragma once + +#include "hls_ap_axi_sdata.hh" +#include "hls_stream.hh" +#include "util.hh" + +template +using Axi = ap_axiu; + +template +using AxiStream = ::hls::stream>; + diff --git a/apps/axistream/src/axi_stream_reader.hh b/apps/axistream/src/axi_stream_reader.hh new file mode 100644 index 0000000..92e360b --- /dev/null +++ b/apps/axistream/src/axi_stream_reader.hh @@ -0,0 +1,168 @@ +#pragma once + +#include +#include "hls_macros.hh" +#include "util.hh" +#include "axi_stream_common.hh" +#include "message.hh" + +/* + * Reads arbitrary length data (in bytes) from an AXI stream. + * This adheres to the Xilinx AXI Reference Guide UG 761: + * https://www.xilinx.com/support/documentation/ip_documentation/ug761_axi_reference_guide.pdf + * + * This standard differs from the ARM standard + * (https://static.docs.arm.com/ihi0051/a/IHI0051A_amba4_axi4_stream_v1_0_protocol_spec.pdf) + * in the following key ways: + * TKEEP: is ignored, since null bytes are only used in a packet with TLAST set, and the user of this API requests a packet by length. + * TID, TDEST, TUSER: are all currently ignored, even though Xilinx supports them. Note that the ap_axiu type requires a bit width of at + * least 1 for all of these signals, even though they are unused, in accordance with UG 761. + * (Also, the tools won't accept 0 bit signals in Verilog anyway, but will delete unused 1 bit signals). + */ +template +class AxiStreamReader { +public: + AxiStreamReader(AxiStream& stream) : stream_(stream) { + HLS_PRAGMA(HLS inline); + reset(); + } + + /** + * Reads all remaining data from the stream until the last packet. + */ + void drain() { + HLS_PRAGMA(HLS inline); + while (!last_) { + HLS_PRAGMA(HLS loop_tripcount min=0); + last_ = stream_.read().last; + } + reset(); + } + + /* + * Reads a message::Struct from the stream + */ + template + T read() { + HLS_PRAGMA(HLS inline); + // Messages may be a non-integer number of bytes in length, since in general they can + // have fields which are not integer numbers of bytes. + static_assert(T::width % util::BITS_PER_BYTE == 0, "Type must be a integer number of bytes"); + return T::from_raw(read_raw()); + } + + /* + * Given some message T which starts with header H, reads all non-header bytes + * of T from the stream, then concatenates the new bytes with the given header + * to return a complate T. + * + * This overload works for types T which are strictly larger than the headers. + */ + template + typename std::enable_if<(T::width > H::width), T>::type read_after(const H& header) { + HLS_PRAGMA(HLS inline); + const int to_read = T::width - H::width; + static_assert(to_read % util::BITS_PER_BYTE == 0, "Remainder must be an integer number of bytes"); + return T::from_raw((read_raw(), H::to_raw(header))); + } + + /* + * Given some message T which starts with header H, reads all non-header bytes + * of T from the stream, then concatenates the new bytes with the given header + * to return a complate T. + * + * This overload works for types T which are the same size as the header - in other words + * the type consists of only a header. + */ + template + typename std::enable_if::type read_after(const H& header) { + HLS_PRAGMA(HLS inline); + return T::from_raw(H::to_raw(header)); + } + + /* + * Reads the specified number of byes from the stream. + */ + template + util::Bytes read_raw() { + HLS_PRAGMA(HLS inline); + // The size of the working buffer. + // This handles the worst-case where we have DataWidth - 1 bytes in buf_, + // and so the last byte of the message data will be at byte index DataWidth - 1 + MsgWidth - 1 + // (we will need to store DataWidth - 1 + MsgWidth bytes). + // The work size, in the last data beat read, the high byte will not be part of this message, + // but we still must store it in the work buffer. + // Thus the work buffer neesd to be DataWidth + MsgWidth bytes. + const int work_size = util::BITS_PER_BYTE * (DataWidth + MsgWidth); + // Maximum number of beats required to get the whole message = ceil(MsgWidth / DataWidth) + // It is possible the last beat isn't needed depending on how much is stored in buf_ + // from the last read + const int max_beats = (MsgWidth + DataWidth - 1) / DataWidth; + // Maximum number of new bytes we might receive minus 1 + // This is the maximal value received may take as of the end of the second to last + // loop iteration, and as such the maximal value received needs to ever hold + // (it doesn't matter if it overflows during the last loop iteration, because it will + // never be read again). + const int max_received = DataWidth * max_beats - 1; + // Type which can store the number of bytes to shift the final result by + // At most, all the low DataWidth bytes need to be shifted + typedef ap_uint::value> Shift; + + // Buf will be zero extended since work is MsgWidth longer than buf_ + ap_uint work = buf_; + // Represents the number of bytes we have so far + ap_uint::value> received = len_; + // Represents the index in work where the last byte of the message will be written + ap_uint::value> last_index = DataWidth - len_ + MsgWidth - 1; + + for (int i = 0; i < max_beats; i++) { + HLS_PRAGMA(HLS unroll); + // Not including this if statement in the loop guard allows Vivado to generate better + // code because then the loop has a fixed number of iterations. + if (received < MsgWidth) { + Axi din = stream_.read(); + buf_ = din.data; + last_ = din.last; + + // This assignment will truncate the most significant bits + ap_uint shifted = ap_uint(din.data) << (util::BITS_PER_BYTE * (i + 1) * DataWidth); + work |= shifted; + received += DataWidth; + + // Incrementally computes last_index % DataWidth + // (ensures that last_index < DataWidth by the time the loop finishes). + last_index -= DataWidth; + } + } + + // Shift the work buffer right to trim off the extra leading bytes, + // and assign it to the correct size vector to truncate any higher bytes + Shift leading_bytes = Shift(DataWidth) - Shift(len_); + util::Bytes result = work >> (leading_bytes * util::BITS_PER_BYTE); + + // Update len to be the number of new bytes now stored in buf_ + len_ = DataWidth - last_index - 1; + + return result; + } + +private: + void reset() { + HLS_PRAGMA(HLS inline); + buf_ = 0; + len_ = 0; + last_ = false; + } + + typedef ap_uint::value> Len; + + // High len_ bytes contain leftover data + util::Bytes buf_; + Len len_; + + // True if the data currently in buf_ was the end of an AXI packet + bool last_; + + AxiStream& stream_; +}; + diff --git a/apps/axistream/src/axi_stream_writer.hh b/apps/axistream/src/axi_stream_writer.hh new file mode 100644 index 0000000..e690e64 --- /dev/null +++ b/apps/axistream/src/axi_stream_writer.hh @@ -0,0 +1,98 @@ +#pragma once + +#include "hls_macros.hh" +#include "util.hh" +#include "axi_stream_common.hh" +#include "message.hh" + +/* + * Writes arbitrary length data to an AXI stream. + * This adheres to the Xilinx AXI Reference Guide UG 761: https://www.xilinx.com/support/documentation/ip_documentation/ug761_axi_reference_guide.pdf. + * For more details, see AxiStreamReader (axi_stream_reader.hh). + */ +template +class AxiStreamWriter { +public: + AxiStreamWriter(AxiStream& stream) : stream_(stream) { + HLS_PRAGMA(HLS inline); + reset(); + } + + /* + * Writes the specified message to the stream, and asserts the last signal on the last packet of the message + * if last is set. + */ + template + void write(const T& in, bool last) { + HLS_PRAGMA(HLS inline); + write_raw(T::to_raw(in), last); + } + + /* + * Writes the specified data to the stream. + * The data must be an integer number of bytes. + * If last is set, then asserts the last signal on the last packet written to the stream. + * + * Note: this takes an ap_uint instead of a util::Bytes such that template argument deduction works. + */ + template + void write_raw(ap_uint msg, bool last) { + HLS_PRAGMA(HLS inline); + static_assert(MsgWidthBits % util::BITS_PER_BYTE == 0, "Type must be a integer number of bytes"); + const int MsgWidth = MsgWidthBits / util::BITS_PER_BYTE; + + // Maximum number of beats we might buffer or send = ceil((MsgWidth + DataWidth - 1) / DataWidth) + // = ceil((MsgWidth - 1) / DataWidth) + 1 + // Uses the fact that ceil(a / b) = (a + b - 1) / b under integer division and without overflow. + const int max_beats = (MsgWidth + DataWidth - 2) / DataWidth + 1; + + // Number of bytes which still have to be either buffered or sent + ap_uint::value> remaining = MsgWidth; + + for (int i = 0; i < max_beats; i++) { + HLS_PRAGMA(HLS unroll); + // Not including this if statement in the loop guard allows Vivado to generate better + // code because then the loop has a fixed number of iterations. + if (remaining != 0) { + // Put the low bytes of msg into the high bytes (unused bytes) of the buffer + util::Bytes to_add = util::Bytes(msg) << (len_ * util::BITS_PER_BYTE); + buf_ |= to_add; + // Figure out how many bytes were added + Len bytes_added = DataWidth - len_; + if (bytes_added > remaining) { + bytes_added = remaining; + } + msg >>= bytes_added * util::BITS_PER_BYTE; + remaining -= bytes_added; + len_ += bytes_added; + + // If we have a full frame, or the final frame, send it + bool is_last = remaining == 0 && last; + if (len_ == DataWidth || is_last) { + Axi dout; + dout.data = buf_; + dout.last = is_last; + dout.keep = (~ap_uint(0)) >>= (DataWidth - len_); + stream_.write(dout); + reset(); + } + } + } + } + +private: + void reset() { + HLS_PRAGMA(HLS inline); + len_ = 0; + buf_ = 0; + } + + typedef ap_uint::value> Len; + + // low len_ bytes are used + util::Bytes buf_; + Len len_; + + AxiStream& stream_; +}; + diff --git a/apps/axistream/src/hls_ap_axi_sdata.hh b/apps/axistream/src/hls_ap_axi_sdata.hh new file mode 100644 index 0000000..48b2538 --- /dev/null +++ b/apps/axistream/src/hls_ap_axi_sdata.hh @@ -0,0 +1,4 @@ +#pragma once + +#pragma GCC system_header +#include "ap_axi_sdata.h" diff --git a/apps/axistream/src/hls_int.hh b/apps/axistream/src/hls_int.hh new file mode 100644 index 0000000..e963483 --- /dev/null +++ b/apps/axistream/src/hls_int.hh @@ -0,0 +1,4 @@ +#pragma once + +#pragma GCC system_header +#include "ap_int.h" diff --git a/apps/axistream/src/hls_macros.hh b/apps/axistream/src/hls_macros.hh new file mode 100644 index 0000000..fad43d1 --- /dev/null +++ b/apps/axistream/src/hls_macros.hh @@ -0,0 +1,8 @@ +#pragma once + +#define HLS_PRAGMA_SUB(x) _Pragma (#x) +#ifdef __SYNTHESIS__ +#define HLS_PRAGMA(x) HLS_PRAGMA_SUB(x) +#else +#define HLS_PRAGMA(x) +#endif diff --git a/apps/axistream/src/hls_stream.hh b/apps/axistream/src/hls_stream.hh new file mode 100644 index 0000000..9ddbb53 --- /dev/null +++ b/apps/axistream/src/hls_stream.hh @@ -0,0 +1,4 @@ +#pragma once + +#pragma GCC system_header +#include "hls_stream.h" diff --git a/apps/axistream/src/message.hh b/apps/axistream/src/message.hh new file mode 100644 index 0000000..37f5cdc --- /dev/null +++ b/apps/axistream/src/message.hh @@ -0,0 +1,498 @@ +#pragma once + +#include +#include +#include "hls_macros.hh" +#include "hls_int.hh" +#include "array.hh" +#include "util.hh" + +/* + * Contrains a series of classes to support interpretation of bit vectors. + * + * Example usage: + * Consider the C struct: + * struct MyStruct { + * uint64_t a; + * int32_t b; + * }; + * + * This struct can be represented in the message framework as: + * struct A : Int<64, Sign::UNSIGNED, Endianness::LITTLE> {}; + * struct B : Int<32, Sign::SIGNED, Endianness::LITTLE> {}; + * using MyStruct = Struct; + * + * It can then be used as: + * MyStruct x; + * x.set(3); + * ap_uint<64> y = x.get(); + * + * All messages are backed by a bitvector, x.data. + * In this case, if the user received a packed 96 bit vector, + * they can unpack it into the struct with: + * x = MyStruct::from_raw() + * Then, they can easily get and set to fields within this packed representation. + * There is no overhead to this at all - it is equivlent to breaking the input vector + * up into local variables manually and loading them into the C struct described above. + * + * In particular, these messages can be easily used with the AxiStreamReader and AxiStreamWriter classes. + * The read/write methods enable reading/writing directly to/from an AXI stream into/from message types. + * + * For convience, the types i8, li16, li32, li64, bi16, bi32, bi64, i8, li16, li32, li64, bi16, bi32 and bi64 + * are provided. These types are integers of the specified length, sign (indicated by i or u), and endianness + * (inidicated by l or b). + * Using templates are also provided for li, lu, bi, bu, lx, and + * bx. + * + * Both nesting and unions are supported. For example: + * + * struct First : MyStruct {}; + * struct Second : Int<32, Sign::UNSIGNED, Endianness::LITTLE> {}; + * using MyPair = Struct; + * + * The MyPair struct can then be used as: + * MyPair y; + * y.set(x); + * y.set(3); + * + * MyStruct z = y.get(); + * + * Unions are also supported, and work as C unions do. + * All fields have an offset of 0, and thus overlap. + * The size of the union is the size of the biggest field. + * + * Enums are supported as well: + * enum class MyEnum : uint8_t { + * THING1, + * THING2 + * }; + * + * struct MyEnumField : Enum {}; + * + * Arrays are also supported: + * struct MyArrayField : Array> {}; + * + * The array type provides operator[] to get and set elements within the array. + * + * In general: + * * Create fields by subclassing their type + * * Create types by defining alias with a using statement + */ +namespace message { + +enum class Sign { + UNSIGNED, SIGNED +}; + +enum class Endianness { + BIG, LITTLE +}; + +template +T flip_endianness(const T& x) { + static_assert(T::width % util::BITS_PER_BYTE == 0, "Type must be a integer number of bytes"); + HLS_PRAGMA(HLS inline); + const int num_bytes = T::width / util::BITS_PER_BYTE; + T out = 0; + for (int i = 0; i < num_bytes; i++) { + HLS_PRAGMA(HLS unroll); + int j = num_bytes - i - 1; + out(util::BITS_PER_BYTE * (i + 1) - 1, util::BITS_PER_BYTE * i) = x(util::BITS_PER_BYTE * (j + 1) - 1, util::BITS_PER_BYTE * j); + } + return out; +} + +/* + * Endianness Converter + */ +template +struct EndiannessConverter {}; + +template <> +struct EndiannessConverter { + template + static ap_uint convert(const ap_uint& x) { + HLS_PRAGMA(HLS inline); + return x; + } + +}; + +template <> +struct EndiannessConverter { + template + static ap_uint convert(const ap_uint& x) { + HLS_PRAGMA(HLS inline); + return flip_endianness(x); + } +}; + +/* + * Helper type to select between ap_uint and ap_int + */ +template +struct TypeSelector {}; + +template +struct TypeSelector { + typedef ap_int type; +}; + +template +struct TypeSelector { + typedef ap_uint type; +}; + +template <> struct TypeSelector<8, Sign::SIGNED> { typedef int8_t type; }; +template <> struct TypeSelector<16, Sign::SIGNED> { typedef int16_t type; }; +template <> struct TypeSelector<32, Sign::SIGNED> { typedef int32_t type; }; +template <> struct TypeSelector<64, Sign::SIGNED> { typedef int64_t type; }; +template <> struct TypeSelector<8, Sign::UNSIGNED> { typedef uint8_t type; }; +template <> struct TypeSelector<16, Sign::UNSIGNED> { typedef uint16_t type; }; +template <> struct TypeSelector<32, Sign::UNSIGNED> { typedef uint32_t type; }; +template <> struct TypeSelector<64, Sign::UNSIGNED> { typedef uint64_t type; }; + +/* + * Type definitions. All types must define: + * width: the width in bits + * raw: the type of a raw field - ap_uint. Note that even + * though the raw value of a field might have the same type as the nominal + * type (given by type, below), it may not be equal in value. + * For example, the endianness may be flipped. + * type: the type of a field when used normally. For integer fields, + * this is ap_int or ap_uint. For enum fields, + * this is the enum type. For composite fields (fields with a struct + * or union type), this is a message of that type. + * to_raw: function from the nominal type to the raw type + * from_raw: function from the raw type to the nominal type + */ + +template +struct Int { + static const int width = W; + + typedef ap_uint raw; + typedef typename TypeSelector::type type; + + static raw to_raw(const type& x) { + HLS_PRAGMA(HLS inline); + return EndiannessConverter::convert(raw(x)); + } + + static type from_raw(const raw& x) { + HLS_PRAGMA(HLS inline); + return EndiannessConverter::convert(x); + } +}; + +template using lx = Int; +template using bx = Int; + +template using li = lx; +template using lu = lx; +template using bi = bx; +template using bu = bx; + +using i8 = li<8>; +using li16 = li<16>; +using li32 = li<32>; +using li64 = li<64>; +using bi16 = bi<16>; +using bi32 = bi<32>; +using bi64 = bi<64>; + +using u8 = lu<8>; +using lu16 = lu<16>; +using lu32 = lu<32>; +using lu64 = lu<64>; +using bu16 = bu<16>; +using bu32 = bu<32>; +using bu64 = bu<64>; + +struct Bool { + static const int width = 1; + + typedef ap_uint raw; + typedef bool type; + + static raw to_raw(const type& x) { + HLS_PRAGMA(HLS inline); + return raw(x); + } + + static type from_raw(const raw& x) { + HLS_PRAGMA(HLS inline); + return type(x); + } +}; + +struct Char { + static const int width = util::BITS_PER_BYTE; + + typedef ap_uint raw; + typedef char type; + + static raw to_raw(const type& x) { + HLS_PRAGMA(HLS inline); + return raw(x); + } + + static type from_raw(const raw& x) { + HLS_PRAGMA(HLS inline); + return type(x); + } +}; + +// std::underlying_type does not translate, +// but vivado accepts this, which is probably equivelent. +template +struct underlying_type_w { + typedef typename std::conditional::value, typename std::make_signed::type, typename std::make_unsigned::type>::type type; +}; + +template ::type)> +struct Enum { + static_assert(std::is_enum::value, "T must be an enum"); + static const int width = W; + + typedef ap_uint raw; + typedef T type; + + typedef typename underlying_type_w::type underlying; + + static raw to_raw(const type& x) { + HLS_PRAGMA(HLS inline); + return EndiannessConverter::convert(raw(static_cast(x))); + } + + static type from_raw(const raw& x) { + HLS_PRAGMA(HLS inline); + return T(static_cast(EndiannessConverter::convert(x))); + } +}; + +/* + * Simple array type for use with messages. + * T is the element type, and must be another message type (either Int, Enum, Struct, Union, or Array) + * N is the number of elements. + */ +template +struct Array { + static const int width = T::width * N; + + typedef ap_uint raw; + typedef util::array type; + + static const int size = N; + + static raw to_raw(const type& x) { + HLS_PRAGMA(HLS inline); + raw result(0); + for (int i = 0; i < N; i++) { + HLS_PRAGMA(HLS unroll); + result(T::width * (i + 1) - 1, T::width * i) = T::to_raw(x[i]); + } + return result; + } + + static type from_raw(const raw& x) { + HLS_PRAGMA(HLS inline); + type result; + for (int i = 0; i < N; i++) { + HLS_PRAGMA(HLS unroll); + typename T::raw slice = x(T::width * (i + 1) - 1, T::width * i); + result[i] = T::from_raw(slice); + } + return result; + } +}; + + +/* + * Helper type to find the width of a list of fields + */ +template +struct StructWidth { + static const int width = H::width + StructWidth::width; +}; + +template +struct StructWidth { + static const int width = H::width; +}; + +/* + * Helper type to determine the offset of a field in a struct. + * F is the field type, and the list H, T... are the fields. + */ +template +struct StructOffsetOf { + static const int offset = H::width + StructOffsetOf::offset; +}; + +template +struct StructOffsetOf { + static const int offset = 0; +}; + +struct StructContainer { + template + using Width = StructWidth; + + template + using OffsetOf = StructOffsetOf; +}; + +/* + * Helper type to find the maximum width width of a list of fields + */ +template +struct UnionWidth { + static const int width = H::width > UnionWidth::width ? H::width : UnionWidth::width; +}; + +template +struct UnionWidth { + static const int width = H::width; +}; +/* + * Helper type to determine the offset of a field in a union. + * For any field that is actually in the union, the offset is 0, + * and if the field isn't in the union this won't compile + */ +template +struct UnionOffsetOf { + static const int offset = UnionOffsetOf::offset; +}; + +template +struct UnionOffsetOf { + static const int offset = 0; +}; + +struct UnionContainer { + template + using Width = UnionWidth; + + template + using OffsetOf = UnionOffsetOf; +}; + +template +struct Setter {}; + +template +struct Setter { + template + static void set(M& m, const TH& value, const T&... rest) { + HLS_PRAGMA(HLS inline); + m.template set(value); + Setter::set(m, rest...); + } +}; + +template +struct Setter { + static void set(M&) { + HLS_PRAGMA(HLS inline); + } +}; + +template +struct Message { + static const int width = C::template Width::width; + typedef Message type; + + typedef ap_uint raw; + raw data; + + Message(const raw& value = 0) : data(value) { + HLS_PRAGMA(HLS inline); + } + + /* + * Writes the specified value into the field. + */ + template + void set_raw(const typename F::raw& value) { + HLS_PRAGMA(HLS inline); + data(F::width + C::template OffsetOf::offset - 1, C::template OffsetOf::offset) = value; + } + + /* + * Reads the specified field as a raw value + */ + template + typename F::raw get_raw() const { + HLS_PRAGMA(HLS inline); + return data(F::width + C::template OffsetOf::offset - 1, C::template OffsetOf::offset); + } + + /* + * Writes the specified field with a value. Since the field is not primitive, + * the value must be a Message -- it must have the same type as the field. + */ + template + void set(const typename F::type& value) { + HLS_PRAGMA(HLS inline); + set_raw(F::to_raw(value)); + } + + /* + * Reads a field as a message of the correct type + */ + template + typename F::type get() const { + HLS_PRAGMA(HLS inline); + return F::from_raw(get_raw()); + } + + bool operator==(const type& other) const { + return data == other.data; + } + + bool operator!=(const type& other) const { + return data != other.data; + } + + template + static type make(const A&... args) { + HLS_PRAGMA(HLS inline); + type result; + Setter::set(result, args...); + return result; + } + + static raw to_raw(const type& x) { + HLS_PRAGMA(HLS inline); + return x.data; + } + + static type from_raw(const raw& x) { + HLS_PRAGMA(HLS inline); + return type(x); + } +}; + +/* + * While the using declarations below are nicer than the structs + * below, (because then you can type Struct instead of + * Struct::type), Vivado crashes if you use them. + * + * template + * using Struct = Message; + * + * template + * using Union = Message; +*/ + +template +struct Struct { + typedef Message type; +}; +template +struct Union { + typedef Message type; +}; + +} diff --git a/apps/axistream/src/util.hh b/apps/axistream/src/util.hh new file mode 100644 index 0000000..6f09fcb --- /dev/null +++ b/apps/axistream/src/util.hh @@ -0,0 +1,29 @@ +#pragma once + +#include "hls_int.hh" +#include "hls_macros.hh" + +namespace util { + +constexpr int BITS_PER_BYTE = 8; + +template +using Bytes = ap_uint; + +template +struct clog2 { + static_assert(num >= 2, "num >= 2"); + static const int value = clog2<(num + 1) / 2>::value + 1; +}; + +template <> +struct clog2<2> { + static const int value = 1; +}; + +template +struct unsigned_bit_width { + static const int value = clog2::value; +}; + +} diff --git a/apps/axistream/test/axi_stream_integeration_test.cc b/apps/axistream/test/axi_stream_integeration_test.cc new file mode 100644 index 0000000..7403a25 --- /dev/null +++ b/apps/axistream/test/axi_stream_integeration_test.cc @@ -0,0 +1,58 @@ +#include "catch.hpp" +#include "axi_stream_reader.hh" +#include "axi_stream_writer.hh" + +using namespace message; + +/* + * struct header { + * uint16_t message_type; + * }; + * struct message_1 { + * header h1; + * uint16_t value; + * }; + * struct message_2 { + * header h2; + * }; + */ +struct message_type : lu16 {}; +using header = Struct::type; + +struct h1 : header {}; +struct value : lu16 {}; +using message_1 = Struct::type; + +struct h2 : header {}; +using message_2 = Struct::type; + +TEST_CASE("axi_stream_integeration_read_after") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + AxiStreamWriter<8> writer(stream); + + header h; + message_1 m1; + h.set(1); + m1.set

(h); + m1.set(42); + message_2 m2; + h.set(2); + m2.set

(h); + + writer.write(m1, true); + writer.write(m2, true); + + header h_read; + h_read = reader.read
(); + CHECK(h_read == m1.get

()); + message_1 m1_read = reader.read_after(h_read); + CHECK(m1_read == m1); + reader.drain(); + + h_read = reader.read
(); + CHECK(h_read == m2.get

()); + message_2 m2_read = reader.read_after(h_read); + CHECK(m2_read == m2); +} + diff --git a/apps/axistream/test/axi_stream_reader_test.cc b/apps/axistream/test/axi_stream_reader_test.cc new file mode 100644 index 0000000..3210388 --- /dev/null +++ b/apps/axistream/test/axi_stream_reader_test.cc @@ -0,0 +1,101 @@ +#include "catch.hpp" +#include "axi_stream_reader.hh" + + +using namespace message; + +// Writes the bytes offset + 0 to offset + n * D - 1 in n groups +// of D bytes to the axi stream, +// setting the last flag on nth packet. +template +void send_test(AxiStream& stream, int n, int offset = 0) { + for (int x = 0; x < n; x++) { + Axi axi; + for (int i = 0; i < D; i++) { + axi.data(util::BITS_PER_BYTE * (i + 1) - 1, util::BITS_PER_BYTE * i) = offset + x * D + i; + } + axi.last = (x == n - 1); + stream.write(axi); + } +} + +TEST_CASE("axi_stream_reader_basic") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + send_test<8>(stream, 1); + + CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0706050403020100")); +} + +TEST_CASE("axi_stream_reader_small_chunks") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + send_test<8>(stream, 2); + + for (int x = 0; x < 16; x++) { + CHECK(reader.read_raw<1>() == util::Bytes<1>(x)); + } +} + +TEST_CASE("axi_stream_reader_off_width") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + send_test<8>(stream, 3); + + // Read 7 bytes, then 10, which will force 2 more reads, + // and make it combine data from a total of 3 reads. + CHECK(reader.read_raw<7>() == util::Bytes<7>("0x06050403020100")); + CHECK(reader.read_raw<10>() == util::Bytes<10>("0x100F0E0D0C0B0A090807")); + // Make sure the remaining 7 bytes are correct + CHECK(reader.read_raw<7>() == util::Bytes<7>("0x17161514131211")); +} + +TEST_CASE("axi_stream_reader_drain") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + + // Send 2 packets, drop the first, and read the second + send_test<8>(stream, 1); + send_test<8>(stream, 1, 8); + reader.drain(); + CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0F0E0D0C0B0A0908")); +} + +TEST_CASE("axi_stream_reader_drain_partial_read") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + + // Send 2 packets, + // read the first a little bit, then drop it and read the secon + send_test<8>(stream, 1); + send_test<8>(stream, 1, 8); + CHECK(reader.read_raw<4>() == util::Bytes<4>("0x03020100")); + reader.drain(); + CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0F0E0D0C0B0A0908")); +} + +TEST_CASE("axi_stream_reader_drain_multi_partial_read") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + + // Send 2 packets, + // read the first a little bit, then drop it and read the secon + send_test<8>(stream, 3); + send_test<8>(stream, 1, 24); + CHECK(reader.read_raw<12>() == util::Bytes<12>("0x0B0A09080706050403020100")); + reader.drain(); + CHECK(reader.read_raw<8>() == util::Bytes<8>("0x1F1E1D1C1B1A1918")); +} + +TEST_CASE("axi_stream_reader_drain_multi_partial_read_strange_width") { + AxiStream<3> stream; + AxiStreamReader<3> reader(stream); + + // Send 2 packets, + // read the first a little bit, then drop it and read the secon + send_test<3>(stream, 3); + send_test<3>(stream, 1, 9); + CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0706050403020100")); + reader.drain(); + CHECK(reader.read_raw<3>() == util::Bytes<3>("0x0B0A09")); +} diff --git a/apps/axistream/test/axi_stream_writer_test.cc b/apps/axistream/test/axi_stream_writer_test.cc new file mode 100644 index 0000000..9fb7a6c --- /dev/null +++ b/apps/axistream/test/axi_stream_writer_test.cc @@ -0,0 +1,133 @@ +#include "catch.hpp" +#include "axi_stream_writer.hh" + +#pragma GCC diagnostic ignored "-Wparentheses" + +template +util::Bytes keep_mask(const util::Bytes& data, const ap_uint& keep) { + util::Bytes result = data; + for (int i = 0; i < D; i++) { + if (!keep[i]) { + result(util::BITS_PER_BYTE * (i + 1) - 1, util::BITS_PER_BYTE * i) = 0; + } + } + return result; +} + +// Checks to see if keep is continuous +template +bool check_keep(const ap_uint& keep) { + bool keeping = true; + for (int i = 0; i < D; i++) { + if (!keeping && keep[i]) return false; + keeping = keep[i]; + } + return true; +} + +template +void check_stream_contents(AxiStream& stream, const util::Bytes data, const ap_uint& keep, bool last, T... args) { + Axi a = stream.read(); + + // Require continous keeps (both real and expected in case the test is bad) + CHECK(check_keep(a.keep)); + CHECK(check_keep(keep)); + + CHECK(a.keep == keep); + // Mask the data by the expected keep - we already checked that + // the keeps are equal + // This is because data without keep set is don't care + util::Bytes masked_actual = keep_mask(a.data, keep); + util::Bytes masked_expected = keep_mask(data, keep); + CHECK(masked_actual == masked_expected); + CHECK(a.last == last); + + check_stream_contents(stream, args...); +} + +template +void check_stream_contents(AxiStream& stream) { + CHECK(stream.empty()); +}; + +TEST_CASE("axi_stream_writer_basic") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<8>("0x0706050403020100"), true); + check_stream_contents(stream, + util::Bytes<8>("0x0706050403020100"), ap_uint<8>("0xFF"), true + ); +} + +TEST_CASE("axi_stream_writer_partial") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<3>("0x020100"), true); + check_stream_contents(stream, + util::Bytes<8>("0x020100"), ap_uint<8>("0x07"), true + ); +} + +TEST_CASE("axi_stream_writer_partial_packing") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<3>("0x020100"), false); + // Nothing should have been written yet + check_stream_contents<8>(stream); + writer.write_raw(util::Bytes<3>("0x050403"), true); + check_stream_contents(stream, + util::Bytes<8>("0x050403020100"), ap_uint<8>("0x3F"), true + ); +} + +TEST_CASE("axi_stream_writer_multi") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<16>("0x0F0E0D0C0B0A09080706050403020100"), true); + check_stream_contents(stream, + util::Bytes<8>("0x0706050403020100"), ap_uint<8>("0xFF"), false, + util::Bytes<8>("0x0F0E0D0C0B0A0908"), ap_uint<8>("0xFF"), true + ); +} + +TEST_CASE("axi_stream_writer_multi_partial") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<12>("0x0B0A09080706050403020100"), true); + check_stream_contents(stream, + util::Bytes<8>("0x0706050403020100"), ap_uint<8>("0xFF"), false, + util::Bytes<8>("0x0B0A0908"), ap_uint<8>("0x0F"), true + ); +} + +TEST_CASE("axi_stream_writer_multi_partial_packing") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<7>("0x06050403020100"), false); + writer.write_raw(util::Bytes<7>("0x0D0C0B0A090807"), true); + check_stream_contents(stream, + util::Bytes<8>("0x0706050403020100"), ap_uint<8>("0xFF"), false, + util::Bytes<8>("0x0D0C0B0A0908"), ap_uint<8>("0x3F"), true + ); +} + +TEST_CASE("axi_stream_writer_multi_partial_packing_strange_width") { + AxiStream<3> stream; + AxiStreamWriter<3> writer(stream); + + writer.write_raw(util::Bytes<7>("0x06050403020100"), false); + writer.write_raw(util::Bytes<7>("0x0D0C0B0A090807"), true); + check_stream_contents(stream, + util::Bytes<3>("0x020100"), ap_uint<3>("0x7"), false, + util::Bytes<3>("0x050403"), ap_uint<3>("0x7"), false, + util::Bytes<3>("0x080706"), ap_uint<3>("0x7"), false, + util::Bytes<3>("0x0B0A09"), ap_uint<3>("0x7"), false, + util::Bytes<3>("0x000D0C"), ap_uint<3>("0x3"), true + ); +} diff --git a/apps/axistream/test/message_test.cc b/apps/axistream/test/message_test.cc new file mode 100644 index 0000000..8bf04e8 --- /dev/null +++ b/apps/axistream/test/message_test.cc @@ -0,0 +1,66 @@ +#include "catch.hpp" +#include "message.hh" +#include "array.hh" +#include "util.hh" + +using namespace message; + +/* + * struct t1 { + * uint16_t f1; // Little endian + * uint16_t f2; // Big endian + * }; + * struct t2 { + * t1 g1[2]; + * t1 g2; + * uint16_t g3; // Little endian + * }; + */ +struct f1 : lu16 {}; +struct f2 : bu16 {}; +using t1 = Struct::type; + +struct g1 : Array {}; +struct g2 : t1 {}; +struct g3 : lu16 {}; +using t2 = Struct::type; + +TEST_CASE("message_basic") { + t1 x; + x.set(0xABCD); + x.set(0x1234); + CHECK(x.get() == 0xABCD); + CHECK(x.get() == 0x1234); + CHECK(x.get_raw() == 0xABCD); + // f2 is big-endian so the raw representation is flipped + CHECK(x.get_raw() == 0x3412); + // The first field is in the least significant location + CHECK(t1::to_raw(x) == 0x3412ABCD); + + t2 y; + util::array z; + z[0] = x; + x.set(0xBEEF); + x.set(0xDEAD); + z[1] = x; + y.set(z); + x.set(0xCAFE); + x.set(0xBABE); + y.set(x); + y.set(0xF00D); + + CHECK(y.get()[0].get() == 0xABCD); + CHECK(y.get()[0].get() == 0x1234); + CHECK(y.get()[1].get() == 0xBEEF); + CHECK(y.get()[1].get() == 0xDEAD); + CHECK(y.get().get() == 0xCAFE); + CHECK(y.get().get() == 0xBABE); + CHECK(y.get() == 0xF00D); + CHECK(t2::to_raw(y) == util::Bytes<14>("0xF00DBEBACAFEADDEBEEF3412ABCD")); +} + +TEST_CASE("message_make") { + t1 x = t1::make(0xABCD, 0x1234); + CHECK(x.get() == 0xABCD); + CHECK(x.get() == 0x1234); +} diff --git a/apps/axistream/test/test_main.cc b/apps/axistream/test/test_main.cc new file mode 100644 index 0000000..0c7c351 --- /dev/null +++ b/apps/axistream/test/test_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/apps/axistream/tl/tl.cc b/apps/axistream/tl/tl.cc new file mode 100644 index 0000000..6d4f04e --- /dev/null +++ b/apps/axistream/tl/tl.cc @@ -0,0 +1,24 @@ +#include +#include "tl.hh" +#include "axi_stream_reader.hh" +#include "axi_stream_writer.hh" + +void tl(AxiStream<8>& in_stream, AxiStream<8>& output_stream) { + HLS_PRAGMA(HLS interface port=in_stream axis); + HLS_PRAGMA(HLS interface port=output_stream axis); + HLS_PRAGMA(HLS interface ap_ctrl_none port=return); + + AxiStreamReader<8> reader(in_stream); + + uint16_t total = 0; + for (size_t i = 0; i < NUM_INPUTS; i++) { + HLS_PRAGMA(HLS pipeline II=1); + input in = reader.read(); + total += in.get(); + } + + output out; + out.set(total); + AxiStreamWriter<8> writer(output_stream); + writer.write(out, true); +} diff --git a/apps/axistream/tl/tl.hh b/apps/axistream/tl/tl.hh new file mode 100644 index 0000000..e730e98 --- /dev/null +++ b/apps/axistream/tl/tl.hh @@ -0,0 +1,26 @@ +#pragma once + +#include "axi_stream_common.hh" +#include "message.hh" + +/* + * struct input { + * uint16_t values[16]; + * }; + * struct output { + * uint16_t value; + * }; + */ +namespace input_m { + struct value : message::lu16 {}; +} + +using input = message::Struct::type; +namespace output_m { + struct value : message::lu16 {}; +} +using output = message::Struct::type; + +const int NUM_INPUTS = 1024; + +void tl(AxiStream<8>& in_stream, AxiStream<8>& output_stream); diff --git a/apps/axistream/tl/tl_test.cc b/apps/axistream/tl/tl_test.cc new file mode 100644 index 0000000..c43add3 --- /dev/null +++ b/apps/axistream/tl/tl_test.cc @@ -0,0 +1,28 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" +#include "tl.hh" +#include "message.hh" +#include "axi_stream_reader.hh" +#include "axi_stream_writer.hh" + +TEST_CASE("tl") { + AxiStream<8> input_stream; + AxiStream<8> output_stream; + AxiStreamWriter<8> writer(input_stream); + AxiStreamReader<8> reader(output_stream); + + uint16_t expected = 0; + for (size_t i = 0; i < NUM_INPUTS; i++) { + input in; + in.set(i); + writer.write(in, i == NUM_INPUTS - 1); + expected += i; + } + + tl(input_stream, output_stream); + + output out = reader.read(); + reader.drain(); + + CHECK(out.get() == expected); +} From 3f774ef66c7e8006c33b1305ee074e01ff86dee0 Mon Sep 17 00:00:00 2001 From: Jacob Glueck Date: Tue, 10 Dec 2019 10:15:18 -0500 Subject: [PATCH 2/3] add mold example --- apps/axistream/.gitignore | 2 + apps/axistream/Makefile | 43 ++- apps/axistream/README.md | 1 + apps/axistream/mold_example/mold_example.cc | 44 +++ apps/axistream/mold_example/mold_example.hh | 22 ++ .../mold_example/mold_example_test.cc | 49 +++ apps/axistream/mold_example/script.tcl | 32 ++ apps/axistream/mold_example/test_main.cc | 2 + .../axistream/reports/mold_example_csynth.rpt | 240 ++++++++++++++ .../reports/process_packet_csynth.rpt | 238 ++++++++++++++ apps/axistream/src/array.hh | 21 +- apps/axistream/src/axi_stream_common.hh | 18 +- apps/axistream/src/axi_stream_reader.hh | 311 +++++++++++++++--- apps/axistream/src/axi_stream_writer.hh | 32 +- apps/axistream/src/ethernet.hh | 49 +++ apps/axistream/src/hls_ap_axi_sdata.hh | 17 + apps/axistream/src/hls_int.hh | 17 + apps/axistream/src/hls_macros.hh | 17 + apps/axistream/src/hls_stream.hh | 17 + apps/axistream/src/ip.hh | 59 ++++ apps/axistream/src/itch.hh | 97 ++++++ apps/axistream/src/message.hh | 33 +- apps/axistream/src/moldudp64.hh | 35 ++ apps/axistream/src/optional.hh | 102 ++++++ apps/axistream/src/ouch.hh | 43 +++ apps/axistream/src/soup.hh | 33 ++ apps/axistream/src/tcp.hh | 63 ++++ apps/axistream/src/udp.hh | 35 ++ apps/axistream/src/util.hh | 54 ++- .../test/axi_stream_integeration_test.cc | 136 +++++++- apps/axistream/test/axi_stream_reader_test.cc | 35 +- apps/axistream/test/axi_stream_writer_test.cc | 19 ++ apps/axistream/test/message_test.cc | 2 +- apps/axistream/tl/test_main.cc | 2 + apps/axistream/tl/tl.cc | 2 +- apps/axistream/tl/tl.hh | 17 + apps/axistream/tl/tl_test.cc | 3 +- 37 files changed, 1861 insertions(+), 81 deletions(-) create mode 100644 apps/axistream/mold_example/mold_example.cc create mode 100644 apps/axistream/mold_example/mold_example.hh create mode 100644 apps/axistream/mold_example/mold_example_test.cc create mode 100644 apps/axistream/mold_example/script.tcl create mode 100644 apps/axistream/mold_example/test_main.cc create mode 100644 apps/axistream/reports/mold_example_csynth.rpt create mode 100644 apps/axistream/reports/process_packet_csynth.rpt create mode 100644 apps/axistream/src/ethernet.hh create mode 100644 apps/axistream/src/ip.hh create mode 100644 apps/axistream/src/itch.hh create mode 100644 apps/axistream/src/moldudp64.hh create mode 100644 apps/axistream/src/optional.hh create mode 100644 apps/axistream/src/ouch.hh create mode 100644 apps/axistream/src/soup.hh create mode 100644 apps/axistream/src/tcp.hh create mode 100644 apps/axistream/src/udp.hh create mode 100644 apps/axistream/tl/test_main.cc diff --git a/apps/axistream/.gitignore b/apps/axistream/.gitignore index 2fbeeb1..12e6187 100644 --- a/apps/axistream/.gitignore +++ b/apps/axistream/.gitignore @@ -1,3 +1,5 @@ build bin *.swp +mold_example/mold_example +mold_example/mold_remover_stream diff --git a/apps/axistream/Makefile b/apps/axistream/Makefile index 2eb6281..43e5df6 100644 --- a/apps/axistream/Makefile +++ b/apps/axistream/Makefile @@ -1,16 +1,33 @@ -CXXFLAGS = -std=c++11 -Wall -Werror +# +# Copyright 2017 Two Sigma Open Source, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +CXXFLAGS = -std=c++11 -Wall -Werror -Wno-unused-label # Set these CATCH_DIR = ../catch -HLS_INCLUDE_DIR = /u/tsqahfus/local/tools/xilinx/Vivado/2018.3/include/ +HLS_INCLUDE_DIR = /home/tslocal/xilinx/Vivado/2018.3/include/ SRC_DIR = src TEST_DIR = test TL_DIR = tl +MOLD_EXAMPLE_DIR = mold_example BUILD_DIR = build BIN_DIR = bin -INCLUDE_DIRS = $(SRC_DIR) $(TEST_DIR) $(TL_DIR) $(CATCH_DIR) $(HLS_INCLUDE_DIR) +INCLUDE_DIRS = $(SRC_DIR) $(TEST_DIR) $(TL_DIR) $(MOLD_EXAMPLE_DIR) $(CATCH_DIR) $(HLS_INCLUDE_DIR) INCLUDE_FLAGS = $(addprefix -I,$(INCLUDE_DIRS)) SRCS = $(wildcard src/*.cc) @@ -25,17 +42,25 @@ TL_SRCS = $(wildcard tl/*.cc) TL_HDRS = $(wildcard tl/*.hh) TL_OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(patsubst %.cc,%.o,$(TL_SRCS)))) -BINS = $(addprefix $(BIN_DIR)/,test_main tl_main) +MOLD_EXAMPLE_SRCS = $(wildcard mold_example/*.cc) +MOLD_EXAMPLE_HDRS = $(wildcard mold_example/*.hh) +MOLD_EXAMPLE_OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(patsubst %.cc,%.o,$(MOLD_EXAMPLE_SRCS)))) + +BINS = $(addprefix $(BIN_DIR)/,test_main tl_main mold_example_main) -ARTIFACTS = $(OBJS) $(TEST_OBJS) $(TL_OBJS) $(BINS) +ARTIFACTS = $(OBJS) $(TEST_OBJS) $(TL_OBJS) $(MOLD_EXAMPLE_OBJS) $(BINS) all: $(OBJS) $(TEST_OBJS) $(BINS) -$(BIN_DIR)/test_main: $(TEST_OBJS) +$(BIN_DIR)/test_main: $(OBJS) $(TEST_OBJS) @mkdir -p $(BIN_DIR) $(CXX) -o $@ $^ -$(BIN_DIR)/tl_main: $(TL_OBJS) +$(BIN_DIR)/tl_main: $(OBJS) $(TL_OBJS) + @mkdir -p $(BIN_DIR) + $(CXX) -o $@ $^ + +$(BIN_DIR)/mold_example_main: $(OBJS) $(MOLD_EXAMPLE_OBJS) @mkdir -p $(BIN_DIR) $(CXX) -o $@ $^ @@ -51,6 +76,10 @@ $(BUILD_DIR)/%.o : $(TL_DIR)/%.cc $(HDRS) @mkdir -p $(BUILD_DIR) $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCLUDE_FLAGS) +$(BUILD_DIR)/%.o : $(MOLD_EXAMPLE_DIR)/%.cc $(HDRS) + @mkdir -p $(BUILD_DIR) + $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCLUDE_FLAGS) + .PHONY: clean clean: rm -f $(ARTIFACTS) diff --git a/apps/axistream/README.md b/apps/axistream/README.md index 132ab7b..4025fee 100644 --- a/apps/axistream/README.md +++ b/apps/axistream/README.md @@ -4,6 +4,7 @@ * `src`: the library itself * `test`: unit tests * `tl`: a simple top-level function for HLS synthesis +* `mold_example`: an example for parsing NASDAQ ITCH marketdata events over moldudp64. (Based off of: https://github.com/Xilinx/HLS_packet_processing/tree/master/apps/mold_remover_stream) ## Building The tests require [Catch2](https://github.com/catchorg/Catch2). diff --git a/apps/axistream/mold_example/mold_example.cc b/apps/axistream/mold_example/mold_example.cc new file mode 100644 index 0000000..4e88776 --- /dev/null +++ b/apps/axistream/mold_example/mold_example.cc @@ -0,0 +1,44 @@ +#include +#include "mold_example.hh" +#include "axi_stream_reader.hh" +#include "ethernet.hh" +#include "ip.hh" +#include "udp.hh" +#include "moldudp64.hh" +#include "itch.hh" + +void mold_example(AxiStream<8>& in_stream, hls::stream& out) { + HLS_PRAGMA(HLS interface port=in_stream axis); + HLS_PRAGMA(HLS interface port=out ap_fifo); + HLS_PRAGMA(HLS interface ap_ctrl_none port=return); + + AxiStreamReader<8> reader(in_stream); + ethernet::Header e; + ip::v4::Header ip; + udp::Header udp; + moldudp64::Header mold; + + reader.read(e, ip, udp, mold); + if (e.get() != ethernet::ETHER_TYPE::IPV4 || + e.get() != 0xc0ffee || + ip.get() != ip::v4::PROTOCOL::UDP || + ip.get() != 0xdeadbeef || + udp.get() != 0xf00d) { + reader.drain(); + return; + } + +mold_message_loop: + for(int i = 0; i < mold.get(); i++) { + moldudp64::MessageHeader message_header; + itch::Header itch_header; + reader.read(message_header, itch_header); + if(itch_header.get() == itch::MESSAGE_TYPE::STOCK_DIRECTORY) { + reader.read(); + out.write(1); + } else if (itch_header.get() == itch::MESSAGE_TYPE::ADD_ORDER_NO_MPID_ATTRIBUTION) { + reader.read(); + out.write(2); + } + } +} diff --git a/apps/axistream/mold_example/mold_example.hh b/apps/axistream/mold_example/mold_example.hh new file mode 100644 index 0000000..8575ff8 --- /dev/null +++ b/apps/axistream/mold_example/mold_example.hh @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "axi_stream_common.hh" + +void mold_example(AxiStream<8>& in_stream, hls::stream& out); diff --git a/apps/axistream/mold_example/mold_example_test.cc b/apps/axistream/mold_example/mold_example_test.cc new file mode 100644 index 0000000..3a513fc --- /dev/null +++ b/apps/axistream/mold_example/mold_example_test.cc @@ -0,0 +1,49 @@ +#include "catch.hpp" +#include "mold_example.hh" +#include "axi_stream_reader.hh" +#include "axi_stream_writer.hh" +#include "ethernet.hh" +#include "ip.hh" +#include "udp.hh" +#include "moldudp64.hh" +#include "itch.hh" + +TEST_CASE("mold_example") { + AxiStream<8> input_stream; + hls::stream output_stream; + AxiStreamWriter<8> writer(input_stream); + + ethernet::Header e; + ip::v4::Header ip; + udp::Header udp; + moldudp64::Header mold; + + e.set(ethernet::ETHER_TYPE::IPV4); + e.set(0xc0ffee); + ip.set(ip::v4::PROTOCOL::UDP); + ip.set(0xdeadbeef); + udp.set(0xf00d); + mold.set(2); + writer.write(e, false); + writer.write(ip, false); + writer.write(udp, false); + writer.write(mold, false); + + moldudp64::MessageHeader message_header; + itch::Header itch_header; + itch::StockDirectory stock_directory; + itch::AddOrder add_order; + itch_header.set(itch::MESSAGE_TYPE::STOCK_DIRECTORY); + writer.write(message_header, false); + writer.write(itch_header, false); + writer.write(stock_directory, false); + itch_header.set(itch::MESSAGE_TYPE::ADD_ORDER_NO_MPID_ATTRIBUTION); + writer.write(message_header, false); + writer.write(itch_header, false); + writer.write(add_order, true); + + mold_example(input_stream, output_stream); + + CHECK(output_stream.read() == 1); + CHECK(output_stream.read() == 2); +} diff --git a/apps/axistream/mold_example/script.tcl b/apps/axistream/mold_example/script.tcl new file mode 100644 index 0000000..c15de81 --- /dev/null +++ b/apps/axistream/mold_example/script.tcl @@ -0,0 +1,32 @@ +# +# Copyright 2017 Two Sigma Open Source, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +open_project mold_example +set_top mold_example + +set cppflags "-I ../src -I ../../catch -std=gnu++11" +add_files mold_example.cc -cflags "$cppflags" +add_files -tb test_main.cc -cflags "$cppflags" +add_files -tb mold_example_test.cc -cflags "$cppflags" + +open_solution "solution" +set_part {xcvu9p-flgb2104-3-e} +create_clock -period 3.103 -name default +config_rtl -reset_level low +csim_design -compiler clang +csynth_design +cosim_design -compiler clang -trace_level all -ldflags "-L/usr/lib/x86_64-linux-gnu/ -B/usr/lib/x86_64-linux-gnu/" +quit diff --git a/apps/axistream/mold_example/test_main.cc b/apps/axistream/mold_example/test_main.cc new file mode 100644 index 0000000..0c7c351 --- /dev/null +++ b/apps/axistream/mold_example/test_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/apps/axistream/reports/mold_example_csynth.rpt b/apps/axistream/reports/mold_example_csynth.rpt new file mode 100644 index 0000000..dc1ff6b --- /dev/null +++ b/apps/axistream/reports/mold_example_csynth.rpt @@ -0,0 +1,240 @@ + + +================================================================ +== Vivado HLS Report for 'mold_example' +================================================================ +* Date: Mon Dec 9 11:34:02 2019 + +* Version: 2018.3 (Build 2405991 on Thu Dec 06 23:56:15 MST 2018) +* Project: mold_example +* Solution: solution +* Product family: virtexuplus +* Target device: xcvu9p-flgb2104-3-e + + +================================================================ +== Performance Estimates +================================================================ ++ Timing (ns): + * Summary: + +--------+-------+----------+------------+ + | Clock | Target| Estimated| Uncertainty| + +--------+-------+----------+------------+ + |ap_clk | 3.10| 2.266| 0.39| + +--------+-------+----------+------------+ + ++ Latency (clock cycles): + * Summary: + +-----+--------+-----+--------+---------+ + | Latency | Interval | Pipeline| + | min | max | min | max | Type | + +-----+--------+-----+--------+---------+ + | 9| 393219| 9| 393219| none | + +-----+--------+-----+--------+---------+ + + + Detail: + * Instance: + N/A + + * Loop: + +---------------------+-----+--------+----------+-----------+-----------+-----------+----------+ + | | Latency | Iteration| Initiation Interval | Trip | | + | Loop Name | min | max | Latency | achieved | target | Count | Pipelined| + +---------------------+-----+--------+----------+-----------+-----------+-----------+----------+ + |- DRAIN | 0| 0| 1| -| -| 0| no | + |- mold_message_loop | 0| 393210| 4 ~ 6 | -| -| 0 ~ 65535 | no | + +---------------------+-----+--------+----------+-----------+-----------+-----------+----------+ + + + +================================================================ +== Utilization Estimates +================================================================ +* Summary: ++-----------------+---------+-------+---------+---------+-----+ +| Name | BRAM_18K| DSP48E| FF | LUT | URAM| ++-----------------+---------+-------+---------+---------+-----+ +|DSP | -| -| -| -| -| +|Expression | -| -| 0| 896| -| +|FIFO | -| -| -| -| -| +|Instance | -| -| -| -| -| +|Memory | -| -| -| -| -| +|Multiplexer | -| -| -| 326| -| +|Register | -| -| 1001| -| -| ++-----------------+---------+-------+---------+---------+-----+ +|Total | 0| 0| 1001| 1222| 0| ++-----------------+---------+-------+---------+---------+-----+ +|Available | 4320| 6840| 2364480| 1182240| 960| ++-----------------+---------+-------+---------+---------+-----+ +|Utilization (%) | 0| 0| ~0 | ~0 | 0| ++-----------------+---------+-------+---------+---------+-----+ + ++ Detail: + * Instance: + N/A + + * DSP48: + N/A + + * Memory: + N/A + + * FIFO: + N/A + + * Expression: + +-------------------------------------+----------+-------+---+-----+------------+------------+ + | Variable Name | Operation| DSP48E| FF| LUT | Bitwidth P0| Bitwidth P1| + +-------------------------------------+----------+-------+---+-----+------------+------------+ + |i_fu_433_p2 | + | 0| 0| 23| 16| 1| + |last_index_V_4_fu_473_p2 | + | 0| 0| 15| 5| 5| + |last_index_V_1_fu_561_p2 | - | 0| 0| 15| 7| 6| + |last_index_V_fu_447_p2 | - | 0| 0| 15| 5| 5| + |leading_bytes_V_fu_504_p2 | - | 0| 0| 15| 5| 4| + |ap_block_state11 | and | 0| 0| 2| 1| 1| + |ap_condition_733 | and | 0| 0| 2| 1| 1| + |ap_predicate_op121_read_state12 | and | 0| 0| 2| 1| 1| + |ap_predicate_op131_read_state15 | and | 0| 0| 2| 1| 1| + |ap_predicate_op139_write_state15 | and | 0| 0| 2| 1| 1| + |ap_predicate_op141_read_state15 | and | 0| 0| 2| 1| 1| + |ap_predicate_op91_read_state10 | and | 0| 0| 2| 1| 1| + |in_stream_V_data_V_0_load_A | and | 0| 0| 2| 1| 1| + |in_stream_V_data_V_0_load_B | and | 0| 0| 2| 1| 1| + |in_stream_V_last_V_0_load_A | and | 0| 0| 2| 1| 1| + |in_stream_V_last_V_0_load_B | and | 0| 0| 2| 1| 1| + |exitcond_fu_428_p2 | icmp | 0| 0| 13| 16| 16| + |in_stream_V_data_V_0_state_cmp_full | icmp | 0| 0| 8| 2| 1| + |in_stream_V_last_V_0_state_cmp_full | icmp | 0| 0| 8| 2| 1| + |tmp_10_fu_546_p2 | icmp | 0| 0| 11| 8| 7| + |tmp_11_fu_552_p2 | icmp | 0| 0| 11| 8| 7| + |tmp_1_fu_340_p2 | icmp | 0| 0| 13| 16| 12| + |tmp_2_fu_350_p2 | icmp | 0| 0| 24| 48| 24| + |tmp_32_1_fu_479_p2 | icmp | 0| 0| 9| 4| 4| + |tmp_3_fu_366_p2 | icmp | 0| 0| 11| 8| 5| + |tmp_4_fu_382_p2 | icmp | 0| 0| 20| 32| 31| + |tmp_52_3_fu_610_p2 | icmp | 0| 0| 11| 5| 4| + |tmp_5_fu_410_p2 | icmp | 0| 0| 13| 16| 13| + |tmp_64_3_fu_574_p2 | icmp | 0| 0| 11| 5| 5| + |r_V_fu_520_p2 | lshr | 0| 0| 598| 168| 168| + |ap_block_state10 | or | 0| 0| 2| 1| 1| + |ap_block_state12 | or | 0| 0| 2| 1| 1| + |ap_block_state15 | or | 0| 0| 2| 1| 1| + |ap_predicate_op75_read_state10 | or | 0| 0| 2| 1| 1| + |last_index_V_4_1_fu_499_p2 | xor | 0| 0| 6| 5| 6| + |last_index_V_6_3_fu_580_p2 | xor | 0| 0| 7| 6| 7| + |reader_len_V_1_fu_597_p2 | xor | 0| 0| 3| 3| 2| + |reader_len_V_2_fu_590_p2 | xor | 0| 0| 3| 3| 2| + |reader_len_V_fu_530_p2 | xor | 0| 0| 3| 3| 2| + +-------------------------------------+----------+-------+---+-----+------------+------------+ + |Total | | 0| 0| 896| 411| 353| + +-------------------------------------+----------+-------+---+-----+------------+------------+ + + * Multiplexer: + +--------------------------------------------------+----+-----------+-----+-----------+ + | Name | LUT| Input Size| Bits| Total Bits| + +--------------------------------------------------+----+-----------+-----+-----------+ + |ap_NS_fsm | 89| 18| 1| 18| + |ap_phi_mux_p_0103_0_i_i_i1_lcssa_1_phi_fu_235_p4 | 9| 2| 6| 12| + |ap_phi_mux_reader_0_1_be_phi_fu_263_p6 | 15| 3| 64| 192| + |ap_phi_mux_reader_0_3_lcssa_phi_fu_253_p4 | 9| 2| 64| 128| + |ap_phi_mux_reader_0_4_lcssa_phi_fu_244_p4 | 9| 2| 64| 128| + |ap_phi_mux_reader_1_be_phi_fu_279_p6 | 15| 3| 3| 9| + |i3_reg_193 | 9| 2| 16| 32| + |in_stream_TDATA_blk_n | 9| 2| 1| 2| + |in_stream_V_data_V_0_data_out | 9| 2| 64| 128| + |in_stream_V_data_V_0_state | 15| 3| 2| 6| + |in_stream_V_dest_V_0_state | 15| 3| 2| 6| + |in_stream_V_last_V_0_data_out | 9| 2| 1| 2| + |in_stream_V_last_V_0_state | 15| 3| 2| 6| + |out_V_blk_n | 9| 2| 1| 2| + |out_V_din | 15| 3| 32| 96| + |p_0103_0_i_i1_lcssa_reg_204 | 9| 2| 5| 10| + |p_0103_0_i_i_i1_lcssa_1_reg_232 | 9| 2| 6| 12| + |p_0296_0_i_i1_lcssa_reg_213 | 9| 2| 168| 336| + |reader_0_1_be_reg_259 | 15| 3| 64| 192| + |reader_0_1_reg_172 | 9| 2| 64| 128| + |reader_1_be_reg_275 | 15| 3| 3| 9| + |reader_1_reg_182 | 9| 2| 3| 6| + +--------------------------------------------------+----+-----------+-----+-----------+ + |Total | 326| 68| 636| 1460| + +--------------------------------------------------+----+-----------+-----+-----------+ + + * Register: + +---------------------------------+-----+----+-----+-----------+ + | Name | FF | LUT| Bits| Const Bits| + +---------------------------------+-----+----+-----+-----------+ + |ap_CS_fsm | 17| 0| 17| 0| + |i3_reg_193 | 16| 0| 16| 0| + |i_reg_659 | 16| 0| 16| 0| + |in_stream_V_data_V_0_payload_A | 64| 0| 64| 0| + |in_stream_V_data_V_0_payload_B | 64| 0| 64| 0| + |in_stream_V_data_V_0_sel_rd | 1| 0| 1| 0| + |in_stream_V_data_V_0_sel_wr | 1| 0| 1| 0| + |in_stream_V_data_V_0_state | 2| 0| 2| 0| + |in_stream_V_dest_V_0_state | 2| 0| 2| 0| + |in_stream_V_last_V_0_payload_A | 1| 0| 1| 0| + |in_stream_V_last_V_0_payload_B | 1| 0| 1| 0| + |in_stream_V_last_V_0_sel_rd | 1| 0| 1| 0| + |in_stream_V_last_V_0_sel_wr | 1| 0| 1| 0| + |in_stream_V_last_V_0_state | 2| 0| 2| 0| + |last_index_V_1_reg_724 | 6| 0| 6| 0| + |last_index_V_reg_669 | 5| 0| 5| 0| + |leading_bytes_V_reg_698 | 4| 0| 4| 0| + |p_0103_0_i_i1_lcssa_reg_204 | 5| 0| 5| 0| + |p_0103_0_i_i_i1_lcssa_1_reg_232 | 6| 0| 6| 0| + |p_0296_0_i_i1_lcssa_reg_213 | 168| 0| 168| 0| + |p_Result_33_1_reg_646 | 16| 0| 16| 0| + |reader_0_1_be_reg_259 | 64| 0| 64| 0| + |reader_0_1_reg_172 | 64| 0| 64| 0| + |reader_0_2_lcssa_reg_222 | 64| 0| 64| 0| + |reader_0_3_lcssa_reg_250 | 64| 0| 64| 0| + |reader_0_4_lcssa_reg_241 | 64| 0| 64| 0| + |reader_1_be_reg_275 | 3| 0| 3| 0| + |reader_1_reg_182 | 3| 0| 3| 0| + |reader_2_1_reg_163 | 1| 0| 1| 0| + |reader_len_V_reg_708 | 3| 0| 3| 0| + |received_upper_bound_reg_664 | 3| 0| 4| 1| + |reg_308 | 64| 0| 64| 0| + |reg_313 | 64| 0| 64| 0| + |reg_318 | 64| 0| 64| 0| + |reg_323 | 64| 0| 64| 0| + |tmp_10_reg_716 | 1| 0| 1| 0| + |tmp_11_reg_720 | 1| 0| 1| 0| + |tmp_15_reg_703 | 3| 0| 3| 0| + |tmp_1_reg_616 | 1| 0| 1| 0| + |tmp_2_reg_630 | 1| 0| 1| 0| + |tmp_32_1_reg_684 | 1| 0| 1| 0| + |tmp_3_reg_634 | 1| 0| 1| 0| + |tmp_4_reg_638 | 1| 0| 1| 0| + |tmp_52_3_reg_734 | 1| 0| 1| 0| + |tmp_5_reg_642 | 1| 0| 1| 0| + |tmp_64_3_reg_730 | 1| 0| 1| 0| + +---------------------------------+-----+----+-----+-----------+ + |Total | 1001| 0| 1002| 1| + +---------------------------------+-----+----+-----+-----------+ + + + +================================================================ +== Interface +================================================================ +* Summary: ++------------------+-----+-----+--------------+--------------------+--------------+ +| RTL Ports | Dir | Bits| Protocol | Source Object | C Type | ++------------------+-----+-----+--------------+--------------------+--------------+ +|ap_clk | in | 1| ap_ctrl_none | mold_example | return value | +|ap_rst_n | in | 1| ap_ctrl_none | mold_example | return value | +|in_stream_TDATA | in | 64| axis | in_stream_V_data_V | pointer | +|in_stream_TVALID | in | 1| axis | in_stream_V_dest_V | pointer | +|in_stream_TREADY | out | 1| axis | in_stream_V_dest_V | pointer | +|in_stream_TDEST | in | 1| axis | in_stream_V_dest_V | pointer | +|in_stream_TKEEP | in | 8| axis | in_stream_V_keep_V | pointer | +|in_stream_TSTRB | in | 8| axis | in_stream_V_strb_V | pointer | +|in_stream_TUSER | in | 1| axis | in_stream_V_user_V | pointer | +|in_stream_TLAST | in | 1| axis | in_stream_V_last_V | pointer | +|in_stream_TID | in | 1| axis | in_stream_V_id_V | pointer | +|out_V_din | out | 32| ap_fifo | out_V | pointer | +|out_V_full_n | in | 1| ap_fifo | out_V | pointer | +|out_V_write | out | 1| ap_fifo | out_V | pointer | ++------------------+-----+-----+--------------+--------------------+--------------+ + diff --git a/apps/axistream/reports/process_packet_csynth.rpt b/apps/axistream/reports/process_packet_csynth.rpt new file mode 100644 index 0000000..aeaaba6 --- /dev/null +++ b/apps/axistream/reports/process_packet_csynth.rpt @@ -0,0 +1,238 @@ + + +================================================================ +== Vivado HLS Report for 'process_packet' +================================================================ +* Date: Thu Dec 5 15:26:12 2019 + +* Version: 2017.3.1 (Build 2033595 on Fri Oct 20 14:40:16 MDT 2017) +* Project: process_packet +* Solution: solution +* Product family: virtexuplus +* Target device: xcvu9p-flgb2104-3-e + + +================================================================ +== Performance Estimates +================================================================ ++ Timing (ns): + * Summary: + +--------+-------+----------+------------+ + | Clock | Target| Estimated| Uncertainty| + +--------+-------+----------+------------+ + |ap_clk | 3.10| 2.60| 0.39| + +--------+-------+----------+------------+ + ++ Latency (clock cycles): + * Summary: + +-----+-----+-----+-----+---------+ + | Latency | Interval | Pipeline| + | min | max | min | max | Type | + +-----+-----+-----+-----+---------+ + | ?| ?| ?| ?| none | + +-----+-----+-----+-----+---------+ + + + Detail: + * Instance: + N/A + + * Loop: + +---------------------+-----+--------+----------+-----------+-----------+-----------+----------+ + | | Latency | Iteration| Initiation Interval | Trip | | + | Loop Name | min | max | Latency | achieved | target | Count | Pipelined| + +---------------------+-----+--------+----------+-----------+-----------+-----------+----------+ + |- mold_message_loop | 0| 458745| 5 ~ 7 | -| -| 0 ~ 65535 | no | + |- read_rest_loop | ?| ?| 1| 1| 1| ?| yes | + +---------------------+-----+--------+----------+-----------+-----------+-----------+----------+ + + + +================================================================ +== Utilization Estimates +================================================================ +* Summary: ++-----------------+---------+-------+---------+---------+-----+ +| Name | BRAM_18K| DSP48E| FF | LUT | URAM| ++-----------------+---------+-------+---------+---------+-----+ +|DSP | -| -| -| -| -| +|Expression | -| -| 0| 1008| -| +|FIFO | -| -| -| -| -| +|Instance | -| -| -| -| -| +|Memory | -| -| -| -| -| +|Multiplexer | -| -| -| 218| -| +|Register | -| -| 428| -| -| ++-----------------+---------+-------+---------+---------+-----+ +|Total | 0| 0| 428| 1226| 0| ++-----------------+---------+-------+---------+---------+-----+ +|Available | 4320| 6840| 2364480| 1182240| 960| ++-----------------+---------+-------+---------+---------+-----+ +|Utilization (%) | 0| 0| ~0 | ~0 | 0| ++-----------------+---------+-------+---------+---------+-----+ + ++ Detail: + * Instance: + N/A + + * DSP48: + N/A + + * Memory: + N/A + + * FIFO: + N/A + + * Expression: + +----------------------------------+----------+-------+---+-----+------------+------------+ + | Variable Name | Operation| DSP48E| FF| LUT | Bitwidth P0| Bitwidth P1| + +----------------------------------+----------+-------+---+-----+------------+------------+ + |i_1_fu_577_p2 | + | 0| 0| 23| 16| 1| + |reader_m_bytesBuffer_3_fu_599_p2 | + | 0| 0| 11| 3| 3| + |reader_m_bytesBuffer_5_fu_761_p2 | + | 0| 0| 11| 2| 3| + |reader_m_bytesBuffer_7_fu_794_p2 | + | 0| 0| 11| 3| 1| + |lastshift_V_fu_644_p2 | - | 0| 0| 15| 5| 4| + |ap_block_state11 | and | 0| 0| 9| 1| 1| + |ap_block_state13 | and | 0| 0| 9| 1| 1| + |ap_block_state7 | and | 0| 0| 9| 1| 1| + |ap_predicate_op139_read_state11 | and | 0| 0| 9| 1| 1| + |ap_predicate_op188_read_state14 | and | 0| 0| 9| 1| 1| + |ap_predicate_op200_read_state17 | and | 0| 0| 9| 1| 1| + |ap_predicate_op204_write_state17 | and | 0| 0| 9| 1| 1| + |input_V_id_V0_status | and | 0| 0| 9| 1| 1| + |tmp1_fu_487_p2 | and | 0| 0| 9| 1| 1| + |tmp2_fu_498_p2 | and | 0| 0| 9| 1| 1| + |tmp3_fu_492_p2 | and | 0| 0| 9| 1| 1| + |valid_4_fu_503_p2 | and | 0| 0| 9| 1| 1| + |done_reading_fu_545_p2 | icmp | 0| 0| 11| 8| 1| + |exitcond_fu_572_p2 | icmp | 0| 0| 13| 16| 16| + |icmp_fu_593_p2 | icmp | 0| 0| 8| 2| 1| + |tmp_12_fu_768_p2 | icmp | 0| 0| 11| 8| 7| + |tmp_13_fu_774_p2 | icmp | 0| 0| 11| 8| 7| + |tmp_178_1_fu_629_p2 | icmp | 0| 0| 9| 4| 4| + |tmp_1_fu_385_p2 | icmp | 0| 0| 24| 48| 2| + |tmp_216_3_fu_788_p2 | icmp | 0| 0| 11| 5| 4| + |tmp_2_fu_399_p2 | icmp | 0| 0| 13| 16| 12| + |tmp_3_fu_419_p2 | icmp | 0| 0| 11| 8| 5| + |tmp_4_fu_463_p2 | icmp | 0| 0| 20| 32| 32| + |tmp_7_fu_481_p2 | icmp | 0| 0| 20| 32| 32| + |tmp_s_fu_379_p2 | icmp | 0| 0| 24| 48| 48| + |ap_block_state1 | or | 0| 0| 9| 1| 1| + |ap_block_state14 | or | 0| 0| 9| 1| 1| + |ap_block_state17 | or | 0| 0| 9| 1| 1| + |p_tmp_s_fu_449_p2 | or | 0| 0| 9| 1| 1| + |itch_message_data_0_fu_754_p3 | select | 0| 0| 8| 1| 8| + |r_V_fu_531_p3 | select | 0| 0| 8| 1| 8| + |t2_V_1_fu_691_p3 | select | 0| 0| 192| 1| 192| + |t2_V_3_fu_712_p3 | select | 0| 0| 192| 1| 192| + |x_V_fu_733_p3 | select | 0| 0| 192| 1| 192| + |r_V_4_fu_539_p2 | xor | 0| 0| 15| 8| 8| + +----------------------------------+----------+-------+---+-----+------------+------------+ + |Total | | 0| 0| 1008| 293| 799| + +----------------------------------+----------+-------+---+-----+------------+------------+ + + * Multiplexer: + +-----------------------------+----+-----------+-----+-----------+ + | Name | LUT| Input Size| Bits| Total Bits| + +-----------------------------+----+-----------+-----+-----------+ + |ap_NS_fsm | 89| 18| 1| 18| + |done_reading_0_in_i_reg_257 | 9| 2| 8| 16| + |i_reg_266 | 9| 2| 16| 32| + |input_V_data_V_blk_n | 9| 2| 1| 2| + |input_V_dest_V_blk_n | 9| 2| 1| 2| + |input_V_id_V_blk_n | 9| 2| 1| 2| + |input_V_keep_V_blk_n | 9| 2| 1| 2| + |input_V_last_V_blk_n | 9| 2| 1| 2| + |input_V_strb_V_blk_n | 9| 2| 1| 2| + |input_V_user_V_blk_n | 9| 2| 1| 2| + |outputMeta_V_V_blk_n | 9| 2| 1| 2| + |outputMeta_V_V_din | 15| 3| 32| 96| + |p_0126_1_i3_1_reg_289 | 9| 2| 192| 384| + |reader_m_bytesBuffer_fu_188 | 15| 3| 3| 9| + +-----------------------------+----+-----------+-----+-----------+ + |Total | 218| 46| 260| 571| + +-----------------------------+----+-----------+-----+-----------+ + + * Register: + +-----------------------------+-----+----+-----+-----------+ + | Name | FF | LUT| Bits| Const Bits| + +-----------------------------+-----+----+-----+-----------+ + |ap_CS_fsm | 17| 0| 17| 0| + |done_reading_0_in_i_reg_257 | 8| 0| 8| 0| + |i_1_reg_868 | 16| 0| 16| 0| + |i_reg_266 | 16| 0| 16| 0| + |ih_data_16_reg_825 | 8| 0| 8| 0| + |ih_data_17_reg_830 | 8| 0| 8| 0| + |p_0126_1_i3_1_reg_289 | 192| 0| 192| 0| + |p_Result_44_1_reg_860 | 16| 0| 16| 0| + |reader_m_buffer_V_reg_277 | 64| 0| 64| 0| + |reader_m_bytesBuffer_fu_188 | 3| 0| 3| 0| + |s2_V_reg_905 | 1| 0| 1| 0| + |tmp_12_reg_925 | 1| 0| 1| 0| + |tmp_13_reg_929 | 1| 0| 1| 0| + |tmp_178_1_reg_896 | 1| 0| 1| 0| + |tmp_18_reg_910 | 1| 0| 1| 0| + |tmp_19_reg_915 | 1| 0| 1| 0| + |tmp_1_reg_810 | 1| 0| 1| 0| + |tmp_20_reg_920 | 1| 0| 1| 0| + |tmp_216_3_reg_933 | 1| 0| 1| 0| + |tmp_2_reg_815 | 1| 0| 1| 0| + |tmp_3_reg_820 | 1| 0| 1| 0| + |tmp_5_reg_881 | 3| 0| 4| 1| + |tmp_data_V_7_reg_886 | 64| 0| 64| 0| + |tmp_s_reg_805 | 1| 0| 1| 0| + |valid_4_reg_835 | 1| 0| 1| 0| + +-----------------------------+-----+----+-----+-----------+ + |Total | 428| 0| 429| 1| + +-----------------------------+-----+----+-----+-----------+ + + + +================================================================ +== Interface +================================================================ +* Summary: ++------------------------+-----+-----+------------+-----------------+--------------+ +| RTL Ports | Dir | Bits| Protocol | Source Object | C Type | ++------------------------+-----+-----+------------+-----------------+--------------+ +|ap_clk | in | 1| ap_ctrl_hs | process_packet | return value | +|ap_rst_n | in | 1| ap_ctrl_hs | process_packet | return value | +|ap_start | in | 1| ap_ctrl_hs | process_packet | return value | +|ap_done | out | 1| ap_ctrl_hs | process_packet | return value | +|ap_idle | out | 1| ap_ctrl_hs | process_packet | return value | +|ap_ready | out | 1| ap_ctrl_hs | process_packet | return value | +|macAddresst_V | in | 48| ap_none | macAddresst_V | scalar | +|ipAddress_V | in | 32| ap_none | ipAddress_V | scalar | +|port_r | in | 32| ap_none | port_r | scalar | +|input_V_data_V_dout | in | 64| ap_fifo | input_V_data_V | pointer | +|input_V_data_V_empty_n | in | 1| ap_fifo | input_V_data_V | pointer | +|input_V_data_V_read | out | 1| ap_fifo | input_V_data_V | pointer | +|input_V_keep_V_dout | in | 8| ap_fifo | input_V_keep_V | pointer | +|input_V_keep_V_empty_n | in | 1| ap_fifo | input_V_keep_V | pointer | +|input_V_keep_V_read | out | 1| ap_fifo | input_V_keep_V | pointer | +|input_V_strb_V_dout | in | 8| ap_fifo | input_V_strb_V | pointer | +|input_V_strb_V_empty_n | in | 1| ap_fifo | input_V_strb_V | pointer | +|input_V_strb_V_read | out | 1| ap_fifo | input_V_strb_V | pointer | +|input_V_user_V_dout | in | 1| ap_fifo | input_V_user_V | pointer | +|input_V_user_V_empty_n | in | 1| ap_fifo | input_V_user_V | pointer | +|input_V_user_V_read | out | 1| ap_fifo | input_V_user_V | pointer | +|input_V_last_V_dout | in | 1| ap_fifo | input_V_last_V | pointer | +|input_V_last_V_empty_n | in | 1| ap_fifo | input_V_last_V | pointer | +|input_V_last_V_read | out | 1| ap_fifo | input_V_last_V | pointer | +|input_V_id_V_dout | in | 1| ap_fifo | input_V_id_V | pointer | +|input_V_id_V_empty_n | in | 1| ap_fifo | input_V_id_V | pointer | +|input_V_id_V_read | out | 1| ap_fifo | input_V_id_V | pointer | +|input_V_dest_V_dout | in | 1| ap_fifo | input_V_dest_V | pointer | +|input_V_dest_V_empty_n | in | 1| ap_fifo | input_V_dest_V | pointer | +|input_V_dest_V_read | out | 1| ap_fifo | input_V_dest_V | pointer | +|output_V_data_V | in | 64| ap_none | output_V_data_V | pointer | +|output_V_keep_V | in | 8| ap_none | output_V_keep_V | pointer | +|output_V_strb_V | in | 8| ap_none | output_V_strb_V | pointer | +|output_V_user_V | in | 1| ap_none | output_V_user_V | pointer | +|output_V_last_V | in | 1| ap_none | output_V_last_V | pointer | +|output_V_id_V | in | 1| ap_none | output_V_id_V | pointer | +|output_V_dest_V | in | 1| ap_none | output_V_dest_V | pointer | +|outputMeta_V_V_din | out | 32| ap_fifo | outputMeta_V_V | pointer | +|outputMeta_V_V_full_n | in | 1| ap_fifo | outputMeta_V_V | pointer | +|outputMeta_V_V_write | out | 1| ap_fifo | outputMeta_V_V | pointer | ++------------------------+-----+-----+------------+-----------------+--------------+ + diff --git a/apps/axistream/src/array.hh b/apps/axistream/src/array.hh index e275752..55a2cf4 100644 --- a/apps/axistream/src/array.hh +++ b/apps/axistream/src/array.hh @@ -1,8 +1,23 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #include "hls_macros.hh" -namespace util { - /* * Simple array type for use in HLS. * This serves as a translatable version of std::array, @@ -28,5 +43,3 @@ struct array { return data[i]; } }; - -} diff --git a/apps/axistream/src/axi_stream_common.hh b/apps/axistream/src/axi_stream_common.hh index c0cef5d..da252af 100644 --- a/apps/axistream/src/axi_stream_common.hh +++ b/apps/axistream/src/axi_stream_common.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #include "hls_ap_axi_sdata.hh" @@ -9,4 +26,3 @@ using Axi = ap_axiu; template using AxiStream = ::hls::stream>; - diff --git a/apps/axistream/src/axi_stream_reader.hh b/apps/axistream/src/axi_stream_reader.hh index 92e360b..bfcbad9 100644 --- a/apps/axistream/src/axi_stream_reader.hh +++ b/apps/axistream/src/axi_stream_reader.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #include @@ -5,6 +22,124 @@ #include "util.hh" #include "axi_stream_common.hh" #include "message.hh" +#include "optional.hh" + +template +struct ReadResult { + util::Bytes data; + util::uint_t len; +}; + +// Length policys control how the AxiStreamReader handles the keep/last flags on AXI streams +// A LengthPolicy must have the following: +// * static bool more_available(bool last) +// * template using Result +// * template static Result result(util::Bytes data, util::uint_t len) +// * template using TypedResult +// * template static TypedResult typed_result(Result read_result) +// * template static Result prefix_with_raw(util::Bytes prefix, Result read_result) +// * template static Result sub_result(Result result, util::uint_t start_index) +// +// Note that all template parameters are declared on the methods, and not on the struct. This is so a LengthPolicy can be used without specifying parameters. +namespace LengthPolicy { + +// Checked means that the keep/last flags are handled exactly, and the stream reader will return +// both the data read and the length of the data +struct Checked { + static bool more_available(bool last) { + HLS_PRAGMA(HLS inline); + return !last; + } + + template + using Result = ReadResult; + + template + static Result result(util::Bytes data, util::uint_t len) { + HLS_PRAGMA(HLS inline); + return { .data = data, .len = len }; + } + + template + using TypedResult = optional; + + template + static TypedResult typed_result(Result read_result) { + HLS_PRAGMA(HLS inline); + if (read_result.len == MsgWidth) { + return T::from_raw(read_result.data); + } else { + return nullopt(); + } + } + + template + static Result prefix_with_raw(util::Bytes prefix, Result read_result) { + HLS_PRAGMA(HLS inline); + return { .data = (read_result.data, prefix), .len = read_result.len + PrefixWidth }; + } + + template + static Result sub_result(Result result, util::uint_t start_index) { + HLS_PRAGMA(HLS inline); + if (start_index > result.len) { + return { .data = 0, .len = 0 }; + } else { + util::uint_t len; + if (start_index + W <= result.len) { + len = W; + } else { + len = result.len - start_index; + } + return { .data = result.data >> (start_index * util::BITS_PER_BYTE), .len = len }; + } + } +}; + +// Unchecked means that keep/last are ignored - this is potentially unsafe. +// If the steram does not have enough data, an Unchecked read with an AxiStreamReader +// will read past the last flag, into the next packet, to satisfy the read. It will also read +// bytes which are masked off by the keep mask. +// However, if you are sure that the data is there, using Unchecked will generate more efficient hardware, +// as correct keep/last checking requires both extra logic, and complicates the stream-reading loop because +// the loop exit condition becomes data-dependant. +struct Unchecked { + static bool more_available(bool) { + HLS_PRAGMA(HLS inline); + return true; + } + + template + using Result = util::Bytes; + + template + static Result result(util::Bytes data, util::uint_t) { + HLS_PRAGMA(HLS inline); + return data; + } + + template + using TypedResult = T; + + template + static TypedResult typed_result(Result read_result) { + HLS_PRAGMA(HLS inline); + return T::from_raw(read_result); + } + + template + static Result prefix_with_raw(util::Bytes prefix, Result read_result) { + HLS_PRAGMA(HLS inline); + return (read_result, prefix); + } + + template + static Result sub_result(Result result, util::uint_t start_index) { + HLS_PRAGMA(HLS inline); + return result((start_index + W) * util::BITS_PER_BYTE - 1, start_index * util::BITS_PER_BYTE); + } +}; +} /* * Reads arbitrary length data (in bytes) from an AXI stream. @@ -32,6 +167,7 @@ public: */ void drain() { HLS_PRAGMA(HLS inline); + DRAIN: while (!last_) { HLS_PRAGMA(HLS loop_tripcount min=0); last_ = stream_.read().last; @@ -39,16 +175,28 @@ public: reset(); } + /* + * Reads a sequence of messages from a stream. Example: + * message1 m1; + * message2 m2; + * stream.read(m1, m2); + */ + template + void read(typename LengthPolicy::template TypedResult& out, typename LengthPolicy::template TypedResult&... rest) { + HLS_PRAGMA(HLS inline); + const int total = util::Sum::value / util::BITS_PER_BYTE; + assign_sub_result_typed(read_raw(), 0, out, rest...); + } + /* * Reads a message::Struct from the stream */ - template - T read() { + template + typename LengthPolicy::template TypedResult read() { HLS_PRAGMA(HLS inline); - // Messages may be a non-integer number of bytes in length, since in general they can - // have fields which are not integer numbers of bytes. - static_assert(T::width % util::BITS_PER_BYTE == 0, "Type must be a integer number of bytes"); - return T::from_raw(read_raw()); + typename LengthPolicy::template TypedResult out; + read(out); + return out; } /* @@ -58,12 +206,20 @@ public: * * This overload works for types T which are strictly larger than the headers. */ - template - typename std::enable_if<(T::width > H::width), T>::type read_after(const H& header) { + template + typename std::enable_if<(T::width > H::width), typename LengthPolicy::template TypedResult>::type read_after(const H& header) { HLS_PRAGMA(HLS inline); + static_assert(H::width % util::BITS_PER_BYTE == 0, "Header must be an integer number of bytes"); + static_assert(T::width % util::BITS_PER_BYTE == 0, "Total must be an integer number of bytes"); + const int to_read = T::width - H::width; - static_assert(to_read % util::BITS_PER_BYTE == 0, "Remainder must be an integer number of bytes"); - return T::from_raw((read_raw(), H::to_raw(header))); + const int to_read_num_bytes = to_read / util::BITS_PER_BYTE; + const int total_num_bytes = T::width / util::BITS_PER_BYTE; + const int header_num_bytes = H::width / util::BITS_PER_BYTE; + + auto read_result = + LengthPolicy::template prefix_with_raw(H::to_raw(header), read_raw()); + return LengthPolicy::template typed_result(read_result); } /* @@ -74,17 +230,18 @@ public: * This overload works for types T which are the same size as the header - in other words * the type consists of only a header. */ - template + template typename std::enable_if::type read_after(const H& header) { HLS_PRAGMA(HLS inline); return T::from_raw(H::to_raw(header)); } + /* * Reads the specified number of byes from the stream. */ - template - util::Bytes read_raw() { + template + typename LengthPolicy::template Result read_raw() { HLS_PRAGMA(HLS inline); // The size of the working buffer. // This handles the worst-case where we have DataWidth - 1 bytes in buf_, @@ -98,41 +255,55 @@ public: // It is possible the last beat isn't needed depending on how much is stored in buf_ // from the last read const int max_beats = (MsgWidth + DataWidth - 1) / DataWidth; - // Maximum number of new bytes we might receive minus 1 - // This is the maximal value received may take as of the end of the second to last - // loop iteration, and as such the maximal value received needs to ever hold - // (it doesn't matter if it overflows during the last loop iteration, because it will - // never be read again). - const int max_received = DataWidth * max_beats - 1; + // Maximum number of new bytes we might receive + const int max_received = DataWidth * max_beats; // Type which can store the number of bytes to shift the final result by // At most, all the low DataWidth bytes need to be shifted - typedef ap_uint::value> Shift; + using Shift = util::uint_t; // Buf will be zero extended since work is MsgWidth longer than buf_ ap_uint work = buf_; // Represents the number of bytes we have so far - ap_uint::value> received = len_; + using Received = util::uint_t; + Received received = len_; + Received received_upper_bound = len_; // Represents the index in work where the last byte of the message will be written - ap_uint::value> last_index = DataWidth - len_ + MsgWidth - 1; + util::state_t last_index = DataWidth - len_ + MsgWidth - 1; for (int i = 0; i < max_beats; i++) { HLS_PRAGMA(HLS unroll); // Not including this if statement in the loop guard allows Vivado to generate better // code because then the loop has a fixed number of iterations. - if (received < MsgWidth) { - Axi din = stream_.read(); - buf_ = din.data; - last_ = din.last; - - // This assignment will truncate the most significant bits - ap_uint shifted = ap_uint(din.data) << (util::BITS_PER_BYTE * (i + 1) * DataWidth); - work |= shifted; - received += DataWidth; - - // Incrementally computes last_index % DataWidth - // (ensures that last_index < DataWidth by the time the loop finishes). - last_index -= DataWidth; + // Only check received_upper_bound - not the actual received! + // This is because received_upper_bound == received _unless_ last_ is set -- + // each data beat contains DataWidth bytes unless last_ is set + // Using received_upper_bound here instead of received helps the HLS tool + // because then it is evident that the loop only exits on last_, + // and not the uncontrained output of compute_received_delta. + if (received_upper_bound >= Received(MsgWidth) || !LengthPolicy::more_available(last_)) { + break; } + + Axi din = stream_.read(); + buf_ = din.data; + last_ = din.last; + + // This assignment will truncate the most significant bits + ap_uint shifted = ap_uint(din.data) << (util::BITS_PER_BYTE * (i + 1) * DataWidth); + work |= shifted; + + received += compute_received_delta(last_, din.keep); + received_upper_bound += DataWidth; + + // Incrementally computes last_index % DataWidth + // (ensures that last_index < DataWidth by the time the loop finishes). + // Note that if the loop exists early (because LengthPolicy is Checked, + // and it hits the last flag), + // then last_index will be wrong, but that doesn't matter because + // all that will result in is an incorrect len_. + // However, once we've hit last, the only way to read more is to reset, + // which will 0 len_. + last_index -= DataWidth; } // Shift the work buffer right to trim off the extra leading bytes, @@ -143,7 +314,18 @@ public: // Update len to be the number of new bytes now stored in buf_ len_ = DataWidth - last_index - 1; - return result; + if (received > MsgWidth) { + received = MsgWidth; + } + return LengthPolicy::template result(result, received); + } + + template + void read_raw(typename LengthPolicy::template Result& out, typename LengthPolicy::template Result&... rest) { + HLS_PRAGMA(HLS inline); + const int total = util::Sum::value; + typename LengthPolicy::template Result result = read_raw(); + assign_sub_result(result, 0, out, rest...); } private: @@ -154,7 +336,61 @@ private: last_ = false; } - typedef ap_uint::value> Len; + template + void assign_sub_result(const typename LengthPolicy::template Result&, util::uint_t) { + HLS_PRAGMA(HLS inline); + } + + template + void assign_sub_result(const typename LengthPolicy::template Result& result, util::uint_t start_index, typename LengthPolicy::template Result& out, typename LengthPolicy::template Result&... rest) { + HLS_PRAGMA(HLS inline); + assign_sub_result(result, start_index + H, rest...); + out = LengthPolicy::template sub_result(result, start_index); + } + + template + void assign_sub_result_typed(const typename LengthPolicy::template Result&, util::uint_t) { + HLS_PRAGMA(HLS inline); + } + + template + void assign_sub_result_typed(const typename LengthPolicy::template Result& result, util::uint_t start_index, typename LengthPolicy::template TypedResult& out, typename LengthPolicy::template TypedResult&... rest) { + HLS_PRAGMA(HLS inline); + static_assert(H::width % util::BITS_PER_BYTE == 0, "Type must be a integer number of bytes"); + const int num_bytes = H::width / util::BITS_PER_BYTE; + + // Note: it is critical that the recursive call occurs before the assignment! + // If the assignment occurs before the call, it is possible the call _could_ change the value of out + // This is because this function takes a lot of outputs by reference, and out could be (though in practice never will be) + // duplicated later in the argument list. As such, the final value of out isn't clear; it depends on all the subsequent + // recursive calls being invoked. + // However, if you assign out after the call, the final value of out is explicit: it _must_ be the one assigned here. + // As such, this ordering of the call and the assignment prevents the HLS tool from inferring a false dependency, + // and allows it to do all the assignments in parallel. + assign_sub_result_typed(result, start_index + num_bytes, rest...); + out = LengthPolicy::template typed_result(LengthPolicy::template sub_result(result, start_index)); + } + + util::uint_t compute_received_delta(bool last, ap_uint keep) { + HLS_PRAGMA(HLS inline); + if (!last) { + return DataWidth; + } else { + // Consider some examples (assuming DataWidth = 8): + // keep = 11111111 (keep everything) + // add a 0 (011111111), invert (100000000), reverse (000000001), + // and count leading zeros -> 8 + // keep = 00000001 (keep only the first byte) + // add a 0 (000000001), invert (111111110), reverse (011111111), + // and count leading zeros -> 1 + // Note that keep = 00000000 is not valid -- + // last should have been set on the prior beat. But this function will + // return 0, which is correct regardless. + return (~ap_uint(keep)).reverse().countLeadingZeros(); + } + } + + using Len = util::state_t; // High len_ bytes contain leftover data util::Bytes buf_; @@ -165,4 +401,3 @@ private: AxiStream& stream_; }; - diff --git a/apps/axistream/src/axi_stream_writer.hh b/apps/axistream/src/axi_stream_writer.hh index e690e64..adb7d77 100644 --- a/apps/axistream/src/axi_stream_writer.hh +++ b/apps/axistream/src/axi_stream_writer.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #include "hls_macros.hh" @@ -47,7 +64,7 @@ public: const int max_beats = (MsgWidth + DataWidth - 2) / DataWidth + 1; // Number of bytes which still have to be either buffered or sent - ap_uint::value> remaining = MsgWidth; + util::uint_t remaining = MsgWidth; for (int i = 0; i < max_beats; i++) { HLS_PRAGMA(HLS unroll); @@ -80,6 +97,16 @@ public: } } + void flush() { + HLS_PRAGMA(HLS inline); + Axi dout; + dout.data = buf_; + dout.last = true; + dout.keep = (~ap_uint(0)) >>= (DataWidth - len_); + stream_.write(dout); + reset(); + } + private: void reset() { HLS_PRAGMA(HLS inline); @@ -87,7 +114,7 @@ private: buf_ = 0; } - typedef ap_uint::value> Len; + using Len = util::uint_t; // low len_ bytes are used util::Bytes buf_; @@ -95,4 +122,3 @@ private: AxiStream& stream_; }; - diff --git a/apps/axistream/src/ethernet.hh b/apps/axistream/src/ethernet.hh new file mode 100644 index 0000000..030d459 --- /dev/null +++ b/apps/axistream/src/ethernet.hh @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "message.hh" +#include + +namespace ethernet { + using namespace message; + + enum class ETHER_TYPE : uint16_t { + IPV4 = 0x0800 + }; + + // Header: https://en.wikipedia.org/wiki/IEEE_802.1Q + struct DestinationMac : lu<48> {}; + struct SourceMac : lu<48> {}; + // 802.1Q VLAN header + struct TPID : bu16 {}; + struct PCP : lu<3> {}; + struct DEI : lu<1> {}; + struct VID : lu<12> {}; + // End VLAN header + struct EtherType : Enum {}; + + using Header = Struct< + DestinationMac, + SourceMac, + TPID, + PCP, + DEI, + VID, + EtherType>::type; +} diff --git a/apps/axistream/src/hls_ap_axi_sdata.hh b/apps/axistream/src/hls_ap_axi_sdata.hh index 48b2538..f5e61aa 100644 --- a/apps/axistream/src/hls_ap_axi_sdata.hh +++ b/apps/axistream/src/hls_ap_axi_sdata.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #pragma GCC system_header diff --git a/apps/axistream/src/hls_int.hh b/apps/axistream/src/hls_int.hh index e963483..e015966 100644 --- a/apps/axistream/src/hls_int.hh +++ b/apps/axistream/src/hls_int.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #pragma GCC system_header diff --git a/apps/axistream/src/hls_macros.hh b/apps/axistream/src/hls_macros.hh index fad43d1..e766752 100644 --- a/apps/axistream/src/hls_macros.hh +++ b/apps/axistream/src/hls_macros.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #define HLS_PRAGMA_SUB(x) _Pragma (#x) diff --git a/apps/axistream/src/hls_stream.hh b/apps/axistream/src/hls_stream.hh index 9ddbb53..d126e02 100644 --- a/apps/axistream/src/hls_stream.hh +++ b/apps/axistream/src/hls_stream.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #pragma GCC system_header diff --git a/apps/axistream/src/ip.hh b/apps/axistream/src/ip.hh new file mode 100644 index 0000000..841ddee --- /dev/null +++ b/apps/axistream/src/ip.hh @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "message.hh" +#include + +namespace ip { namespace v4 { + using namespace message; + + // Header: https://en.wikipedia.org/wiki/IPv4#Header + // https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers + enum class PROTOCOL : uint8_t { + TCP = 0x06, + UDP = 0x11, + }; + + struct Version : lu<4> {}; + struct IHL : lu<4> {}; + struct DSCP : lu<6> {}; + struct ECN : lu<2> {}; + struct TotalLength : bu16 {}; + struct Identification : bu16 {}; + struct Flags : lu<3> {}; + struct FragmentOffset : lu<13> {}; + struct TimeToLive : u8 {}; + struct Protocol : Enum {}; + struct HeaderChecksum : lu16 {}; + struct SourceIP : lu32 {}; + struct DestinationIP : lu32 {}; + + using Header = Struct< + Version, + IHL, + DSCP, + ECN, + TotalLength, + Identification, + Flags, + FragmentOffset, + TimeToLive, + Protocol, + HeaderChecksum, + SourceIP, + DestinationIP>::type; +}} diff --git a/apps/axistream/src/itch.hh b/apps/axistream/src/itch.hh new file mode 100644 index 0000000..30290d5 --- /dev/null +++ b/apps/axistream/src/itch.hh @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "message.hh" +#include + +namespace itch { + using namespace message; + + enum class MESSAGE_TYPE : uint8_t { + SYSTEM_EVENT = 'S', + STOCK_DIRECTORY = 'R', + ADD_ORDER_NO_MPID_ATTRIBUTION = 'A', + }; + + struct MessageType : Enum {}; + struct StockLocate : bu16 {}; + struct TrackingNumber : bu16 {}; + struct Timestamp : bu<48> {}; + using Header = Struct::type; + + // 4.1 System Event Message + enum class SYSTEM_EVENT_CODE : uint8_t { + START_OF_MESSAGES = 'O', + START_OF_SYSTEM_HOURS = 'S', + START_OF_MARKET_HOURS = 'Q', + END_OF_MARKET_HOURS = 'M', + END_OF_SYSTEM_HOURS = 'E', + END_OF_MESSAGES = 'C', + }; + struct EventCode : Enum{}; + + using SystemEventMessage = Struct::type; + + // 4.2.1 Stock Directory + struct Stock : Array {}; + struct MarketCategory : u8 {}; + struct FinancialStatusIndicator : u8 {}; + struct RoundLotSize : bu32 {}; + struct RoundLotsOnly : u8 {}; + struct IssueClassification : u8 {}; + struct IssueSubType : bu16 {}; + struct Authenticity : u8 {}; + struct ShortSaleThresholdIndicator : u8 {}; + struct IPOFlag : u8 {}; + struct LULDReferencePriceTier : u8 {}; + struct ETPFlag : u8 {}; + struct ETPLeverageFactor : bu32 {}; + struct InverseIndicator : u8 {}; + + using StockDirectory = Struct< + Stock, + MarketCategory, + FinancialStatusIndicator, + RoundLotSize, + RoundLotsOnly, + IssueClassification, + IssueSubType, + Authenticity, + ShortSaleThresholdIndicator, + IPOFlag, + LULDReferencePriceTier, + ETPFlag, + ETPLeverageFactor, + InverseIndicator>::type; + + // 4.3.1 Add Order - No MPID Attribution + struct OrderReferenceNumber : bu64 {}; + struct BuySell : u8 {}; + struct Shares : bu32 {}; + // Stock alredy declared above + // struct Stock : Array {}; + struct Price : bu32 {}; + + using AddOrder = Struct< + OrderReferenceNumber, + BuySell, + Shares, + Stock, + Price>::type; +} diff --git a/apps/axistream/src/message.hh b/apps/axistream/src/message.hh index 37f5cdc..020c2ec 100644 --- a/apps/axistream/src/message.hh +++ b/apps/axistream/src/message.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #include @@ -281,7 +298,7 @@ struct Array { static const int width = T::width * N; typedef ap_uint raw; - typedef util::array type; + typedef array type; static const int size = N; @@ -406,10 +423,15 @@ struct Message { typedef ap_uint raw; raw data; - Message(const raw& value = 0) : data(value) { + explicit Message(const raw& value = 0) : data(value) { HLS_PRAGMA(HLS inline); } + explicit operator raw() const { + HLS_PRAGMA(HLS inline); + return type::to_raw(*this); + } + /* * Writes the specified value into the field. */ @@ -447,11 +469,18 @@ struct Message { return F::from_raw(get_raw()); } + raw to_raw() const { + HLS_PRAGMA(HLS inline); + return type::to_raw(*this); + } + bool operator==(const type& other) const { + HLS_PRAGMA(HLS inline); return data == other.data; } bool operator!=(const type& other) const { + HLS_PRAGMA(HLS inline); return data != other.data; } diff --git a/apps/axistream/src/moldudp64.hh b/apps/axistream/src/moldudp64.hh new file mode 100644 index 0000000..fec861c --- /dev/null +++ b/apps/axistream/src/moldudp64.hh @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "message.hh" +#include + +namespace moldudp64 { + using namespace message; + + // https://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/moldudp64.pdf + struct Session : Array {}; + struct SequenceNumber : bu64 {}; + struct MessageCount : bu16 {}; + + using Header = Struct::type; + + struct MessageLength : bu16 {}; + using MessageHeader = Struct::type; +} diff --git a/apps/axistream/src/optional.hh b/apps/axistream/src/optional.hh new file mode 100644 index 0000000..141374a --- /dev/null +++ b/apps/axistream/src/optional.hh @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once +#include "hls_macros.hh" + +struct nullopt {}; + +/* + * Represents an optional type, much like https://en.cppreference.com/w/cpp/utility/optional. + * However, the std::optional won't synthesize, because it internally uses a reinterpret_cast -- + * instead of storing an instance of type T, it instead stores a byte buffer of the proper size, + * and only constructs an instance of T within that buffer when required. + * As such, reading the value requires a reinterpret_cast. + * + * To avoid casting, this class stores an instance of T. Since it does so, however, + * T must be default constructable -- there is no way to defer construction. + * + * This class is useful for functions which may or may not return a result. + * For example, a get function on a hash table might have the signature: + * optional get(const Key& key) + * This function would either return the value corresponding to the given key, + * if there is a mapping for the given key in the hash table, + * or return an empty optional. + * The caller of get could then inspect the optional with operator bool() to + * determine if the mapping was found. For example: + * + * optional x = ht.get(5); + * if (bool(x)) { + * // 5 was found, extract the value from the optional with * + * std::cout << *x << std::endl; + * } else { + * std::cout << "not found" << std::endl; + * } + */ +template +class optional { +public: + optional() : _valid(false) { + HLS_PRAGMA(HLS inline); + } + optional(const T& value) : _value(value), _valid(true) { + HLS_PRAGMA(HLS inline); + } + optional(nullopt) : optional() { + HLS_PRAGMA(HLS inline); + } + + const T* operator->() const { + HLS_PRAGMA(HLS inline); + return &_value; + } + + T* operator->() { + HLS_PRAGMA(HLS inline); + return &_value; + } + + const T& operator*() const { + HLS_PRAGMA(HLS inline); + return _value; + } + + T& operator*() { + HLS_PRAGMA(HLS inline); + return _value; + } + + explicit operator bool() const { + HLS_PRAGMA(HLS inline); + return _valid; + } +private: + T _value; + bool _valid; +}; + +template +bool operator==(const optional& a, const optional b) { + HLS_PRAGMA(HLS inline); + return (!bool(a) && !bool(b)) || (bool(a) && bool(b) && *a == *b); +} + +template +bool operator!=(const optional& a, const optional b) { + HLS_PRAGMA(HLS inline); + return !(a == b); +} diff --git a/apps/axistream/src/ouch.hh b/apps/axistream/src/ouch.hh new file mode 100644 index 0000000..4244be1 --- /dev/null +++ b/apps/axistream/src/ouch.hh @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "message.hh" +#include + +namespace ouch { + using namespace message; + + // http://www.nasdaqtrader.com/content/technicalsupport/specifications/tradingproducts/ouch4.2.pdf + // Header fields common to every ouch message + enum class MESSAGE_TYPE : uint8_t { + EXECUTED = 'E', + INVALID = 'X' + }; + struct MessageType : Enum {}; + struct Timestamp : bu64 {}; + using Header = Struct::type; + + + struct OrderToken : Array {}; + struct ExecutedShares : bu32 {}; + struct ExecutionPrice : bu32 {}; + struct LiquidityFlag : u8 {}; + struct MatchNumber : bu64 {}; + using OrderExecuted = Struct::type; +} diff --git a/apps/axistream/src/soup.hh b/apps/axistream/src/soup.hh new file mode 100644 index 0000000..e7cad1f --- /dev/null +++ b/apps/axistream/src/soup.hh @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once +#include "message.hh" +#include + +namespace soup { + using namespace message; + + // https://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/soupbintcp.pdf + // Header fields common to every message + enum class PACKET_TYPE : uint8_t { + SEQUENCED_DATA = 'S', + }; + struct PacketLength : bu16 {}; + struct PacketType : Enum {}; + using Header = Struct::type; +} diff --git a/apps/axistream/src/tcp.hh b/apps/axistream/src/tcp.hh new file mode 100644 index 0000000..431e3b7 --- /dev/null +++ b/apps/axistream/src/tcp.hh @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once +#include "message.hh" + +namespace tcp { + using namespace message; + + // Header: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure + struct SourcePort : bu16 {}; + struct DestinationPort : bu16 {}; + struct SeqNumber : bu32 {}; + struct AckNumber : bu32 {}; + struct DataOffset : lu<4> {}; + struct Reserved : lu<3> {}; + struct NS : lu<1> {}; + struct CWR : lu<1> {}; + struct ECE : lu<1> {}; + struct URG : lu<1> {}; + struct ACK : lu<1> {}; + struct PSH : lu<1> {}; + struct RST : lu<1> {}; + struct SYN : lu<1> {}; + struct FIN : lu<1> {}; + struct WindowSize : bu16 {}; + struct Checksum : bu16 {}; + struct UrgentPointer : bu16 {}; + + using Header = Struct< + SourcePort, + DestinationPort, + SeqNumber, + AckNumber, + DataOffset, + Reserved, + NS, + CWR, + ECE, + URG, + ACK, + PSH, + RST, + SYN, + FIN, + WindowSize, + Checksum, + UrgentPointer>::type; +} diff --git a/apps/axistream/src/udp.hh b/apps/axistream/src/udp.hh new file mode 100644 index 0000000..6d1ba25 --- /dev/null +++ b/apps/axistream/src/udp.hh @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once +#include "message.hh" + +namespace udp { + using namespace message; + + // Header: https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure + struct SourcePort : bu16 {}; + struct DestinationPort : bu16 {}; + struct Length : bu16 {}; + struct Checksum : bu16 {}; + + using Header = Struct< + SourcePort, + DestinationPort, + Length, + Checksum>::type; +} diff --git a/apps/axistream/src/util.hh b/apps/axistream/src/util.hh index 6f09fcb..dda5291 100644 --- a/apps/axistream/src/util.hh +++ b/apps/axistream/src/util.hh @@ -1,7 +1,24 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once -#include "hls_int.hh" #include "hls_macros.hh" +#include "hls_int.hh" namespace util { @@ -21,9 +38,44 @@ struct clog2<2> { static const int value = 1; }; +template +struct is_power_of_2 { + static_assert(num >= 0, "num >= 0"); + static const bool value = is_power_of_2::value && (num % 2 == 0); +}; + +template <> +struct is_power_of_2<1> { + static const bool value = true; +}; + +template <> +struct is_power_of_2<0> { + static const bool value = false; +}; + template struct unsigned_bit_width { static const int value = clog2::value; }; +template +using state_t = ap_uint::value>; + +template +using uint_t = ap_uint::value>; + +template +struct Sum; + +template +struct Sum { + static const int value = A + Sum::value; +}; + +template <> +struct Sum<>{ + static const int value = 0; +}; + } diff --git a/apps/axistream/test/axi_stream_integeration_test.cc b/apps/axistream/test/axi_stream_integeration_test.cc index 7403a25..946cc35 100644 --- a/apps/axistream/test/axi_stream_integeration_test.cc +++ b/apps/axistream/test/axi_stream_integeration_test.cc @@ -2,6 +2,7 @@ #include "axi_stream_reader.hh" #include "axi_stream_writer.hh" + using namespace message; /* @@ -43,16 +44,133 @@ TEST_CASE("axi_stream_integeration_read_after") { writer.write(m1, true); writer.write(m2, true); - header h_read; - h_read = reader.read
(); - CHECK(h_read == m1.get

()); - message_1 m1_read = reader.read_after(h_read); - CHECK(m1_read == m1); + optional
h_read = reader.read(); + CHECK(bool(h_read)); + CHECK(*h_read == m1.get

()); + optional m1_read = reader.read_after(*h_read); + CHECK(bool(m1_read)); + CHECK(*m1_read == m1); reader.drain(); - h_read = reader.read
(); - CHECK(h_read == m2.get

()); - message_2 m2_read = reader.read_after(h_read); - CHECK(m2_read == m2); + h_read = reader.read(); + CHECK(bool(h_read)); + CHECK(*h_read == m2.get

()); + optional m2_read = reader.read_after(*h_read); + CHECK(bool(m2_read)); + CHECK(*m2_read == m2); +} + +TEST_CASE("axi_stream_integration_read_checked") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + AxiStreamWriter<8> writer(stream); + + // Send 2 1-byte packets. Each will take an entire 8-byte data beat. + // So 2 8-byte data beats will be sent + writer.write_raw(util::Bytes<1>(0xAB), true); + writer.write_raw(util::Bytes<1>(0xCD), true); + + ReadResult<16> result; + + // Takes 2 reads, 1 per packet! + result = reader.read_raw(); + CHECK(result.data == 0xAB); + CHECK(result.len == 1); + + // Check that at the end of the packet, we can't read anymore until a drain() + result = reader.read_raw(); + CHECK(result.len == 0); + reader.drain(); + + result = reader.read_raw(); + CHECK(result.data == 0xCD); + CHECK(result.len == 1); +} + +TEST_CASE("axi_stream_integration_read_unchecked") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + AxiStreamWriter<8> writer(stream); + + // Send 2 1-byte packets. Each will take an entire 8-byte data beat. + // So 2 8-byte data beats will be sent + writer.write_raw(util::Bytes<1>(0xAB), true); + writer.write_raw(util::Bytes<1>(0xCD), true); + + // Read 16 bytes _unchecked_! This will ignore the last and keep flags, + // and combine the 2 packets. + util::Bytes<16> result = reader.read_raw(); + // Can't check any other bytes -- they are undefined! We only set these 2 bytes. + CHECK(result(7, 0) == 0xAB); + CHECK(result(71, 64) == 0xCD); +} + +TEST_CASE("axi_stream_integration_read_raw_checked_multi") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<1>(0xAB), false); + writer.write_raw(util::Bytes<2>(0xCDEF), false); + writer.write_raw(util::Bytes<8>(0xdeadbeef), true); + + ReadResult<1> a; + ReadResult<2> b; + ReadResult<8> c; + reader.read_raw(a, b, c); + + CHECK(a.data == 0xAB); + CHECK(a.len == 1); + CHECK(b.data == 0xCDEF); + CHECK(b.len == 2); + CHECK(c.data == 0xdeadbeef); + CHECK(c.len == 8); +} + +TEST_CASE("axi_stream_integration_read_raw_unchecked_multi") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<1>(0xAB), false); + writer.write_raw(util::Bytes<2>(0xCDEF), false); + writer.write_raw(util::Bytes<8>(0xdeadbeef), true); + + util::Bytes<1> a; + util::Bytes<2> b; + util::Bytes<8> c; + reader.read_raw(a, b, c); + + CHECK(a == 0xAB); + CHECK(b == 0xCDEF); + CHECK(c == 0xdeadbeef); +} + +TEST_CASE("axi_stream_integeration_read_checked_multi") { + AxiStream<8> stream; + AxiStreamReader<8> reader(stream); + AxiStreamWriter<8> writer(stream); + + header h; + message_1 m1; + h.set(1); + m1.set

(h); + m1.set(42); + + message_1 m2 = m1; + m2.set(43); + + writer.write(m1, false); + writer.write(m2, true); + + optional m1_read; + optional m2_read; + optional m3_read; + reader.read(m1_read, m2_read, m3_read); + CHECK(bool(m1_read)); + CHECK(*m1_read == m1); + CHECK(bool(m2_read)); + CHECK(*m2_read == m2); + CHECK_FALSE(bool(m3_read)); } diff --git a/apps/axistream/test/axi_stream_reader_test.cc b/apps/axistream/test/axi_stream_reader_test.cc index 3210388..31503a4 100644 --- a/apps/axistream/test/axi_stream_reader_test.cc +++ b/apps/axistream/test/axi_stream_reader_test.cc @@ -1,8 +1,10 @@ #include "catch.hpp" #include "axi_stream_reader.hh" +#pragma GCC diagnostic ignored "-Wparentheses" using namespace message; +using namespace LengthPolicy; // Writes the bytes offset + 0 to offset + n * D - 1 in n groups // of D bytes to the axi stream, @@ -15,16 +17,25 @@ void send_test(AxiStream& stream, int n, int offset = 0) { axi.data(util::BITS_PER_BYTE * (i + 1) - 1, util::BITS_PER_BYTE * i) = offset + x * D + i; } axi.last = (x == n - 1); + // Only sending whole beats + axi.keep = ~ap_uint(0); stream.write(axi); } } +template +void check_read_raw(AxiStreamReader& reader, const util::Bytes expected) { + ReadResult result = reader.template read_raw(); + CHECK(result.len == W); + CHECK(result.data == expected); +} + TEST_CASE("axi_stream_reader_basic") { AxiStream<8> stream; AxiStreamReader<8> reader(stream); send_test<8>(stream, 1); - CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0706050403020100")); + check_read_raw<8>(reader, "0x0706050403020100"); } TEST_CASE("axi_stream_reader_small_chunks") { @@ -33,7 +44,7 @@ TEST_CASE("axi_stream_reader_small_chunks") { send_test<8>(stream, 2); for (int x = 0; x < 16; x++) { - CHECK(reader.read_raw<1>() == util::Bytes<1>(x)); + check_read_raw<1>(reader, x); } } @@ -44,10 +55,10 @@ TEST_CASE("axi_stream_reader_off_width") { // Read 7 bytes, then 10, which will force 2 more reads, // and make it combine data from a total of 3 reads. - CHECK(reader.read_raw<7>() == util::Bytes<7>("0x06050403020100")); - CHECK(reader.read_raw<10>() == util::Bytes<10>("0x100F0E0D0C0B0A090807")); + check_read_raw<7>(reader, "0x06050403020100"); + check_read_raw<10>(reader, "0x100F0E0D0C0B0A090807"); // Make sure the remaining 7 bytes are correct - CHECK(reader.read_raw<7>() == util::Bytes<7>("0x17161514131211")); + check_read_raw<7>(reader, "0x17161514131211"); } TEST_CASE("axi_stream_reader_drain") { @@ -58,7 +69,7 @@ TEST_CASE("axi_stream_reader_drain") { send_test<8>(stream, 1); send_test<8>(stream, 1, 8); reader.drain(); - CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0F0E0D0C0B0A0908")); + check_read_raw<8>(reader, "0x0F0E0D0C0B0A0908"); } TEST_CASE("axi_stream_reader_drain_partial_read") { @@ -69,9 +80,9 @@ TEST_CASE("axi_stream_reader_drain_partial_read") { // read the first a little bit, then drop it and read the secon send_test<8>(stream, 1); send_test<8>(stream, 1, 8); - CHECK(reader.read_raw<4>() == util::Bytes<4>("0x03020100")); + check_read_raw<4>(reader, "0x03020100"); reader.drain(); - CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0F0E0D0C0B0A0908")); + check_read_raw<8>(reader, "0x0F0E0D0C0B0A0908"); } TEST_CASE("axi_stream_reader_drain_multi_partial_read") { @@ -82,9 +93,9 @@ TEST_CASE("axi_stream_reader_drain_multi_partial_read") { // read the first a little bit, then drop it and read the secon send_test<8>(stream, 3); send_test<8>(stream, 1, 24); - CHECK(reader.read_raw<12>() == util::Bytes<12>("0x0B0A09080706050403020100")); + check_read_raw<12>(reader, "0x0B0A09080706050403020100"); reader.drain(); - CHECK(reader.read_raw<8>() == util::Bytes<8>("0x1F1E1D1C1B1A1918")); + check_read_raw<8>(reader, "0x1F1E1D1C1B1A1918"); } TEST_CASE("axi_stream_reader_drain_multi_partial_read_strange_width") { @@ -95,7 +106,7 @@ TEST_CASE("axi_stream_reader_drain_multi_partial_read_strange_width") { // read the first a little bit, then drop it and read the secon send_test<3>(stream, 3); send_test<3>(stream, 1, 9); - CHECK(reader.read_raw<8>() == util::Bytes<8>("0x0706050403020100")); + check_read_raw<8>(reader, "0x0706050403020100"); reader.drain(); - CHECK(reader.read_raw<3>() == util::Bytes<3>("0x0B0A09")); + check_read_raw<3>(reader, "0x0B0A09"); } diff --git a/apps/axistream/test/axi_stream_writer_test.cc b/apps/axistream/test/axi_stream_writer_test.cc index 9fb7a6c..1ec27a1 100644 --- a/apps/axistream/test/axi_stream_writer_test.cc +++ b/apps/axistream/test/axi_stream_writer_test.cc @@ -131,3 +131,22 @@ TEST_CASE("axi_stream_writer_multi_partial_packing_strange_width") { util::Bytes<3>("0x000D0C"), ap_uint<3>("0x3"), true ); } + +TEST_CASE("axi_stream_writer_zero_width") { + AxiStream<8> stream; + AxiStreamWriter<8> writer(stream); + + writer.write_raw(util::Bytes<7>("0x06050403020100"), false); + writer.flush(); + + writer.write_raw(util::Bytes<7>("0x0708090A0B0C0D"), false); + writer.flush(); + + writer.flush(); + + check_stream_contents(stream, + util::Bytes<8>("0x06050403020100"), ap_uint<8>("0x7F"), true, + util::Bytes<8>("0x0708090A0B0C0D"), ap_uint<8>("0x7F"), true, + util::Bytes<8>("0x00000000000000"), ap_uint<8>("0x00"), true + ); +} diff --git a/apps/axistream/test/message_test.cc b/apps/axistream/test/message_test.cc index 8bf04e8..a3cf251 100644 --- a/apps/axistream/test/message_test.cc +++ b/apps/axistream/test/message_test.cc @@ -38,7 +38,7 @@ TEST_CASE("message_basic") { CHECK(t1::to_raw(x) == 0x3412ABCD); t2 y; - util::array z; + array z; z[0] = x; x.set(0xBEEF); x.set(0xDEAD); diff --git a/apps/axistream/tl/test_main.cc b/apps/axistream/tl/test_main.cc new file mode 100644 index 0000000..0c7c351 --- /dev/null +++ b/apps/axistream/tl/test_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/apps/axistream/tl/tl.cc b/apps/axistream/tl/tl.cc index 6d4f04e..adb2823 100644 --- a/apps/axistream/tl/tl.cc +++ b/apps/axistream/tl/tl.cc @@ -13,7 +13,7 @@ void tl(AxiStream<8>& in_stream, AxiStream<8>& output_stream) { uint16_t total = 0; for (size_t i = 0; i < NUM_INPUTS; i++) { HLS_PRAGMA(HLS pipeline II=1); - input in = reader.read(); + input in = reader.read(); total += in.get(); } diff --git a/apps/axistream/tl/tl.hh b/apps/axistream/tl/tl.hh index e730e98..ecc3a6d 100644 --- a/apps/axistream/tl/tl.hh +++ b/apps/axistream/tl/tl.hh @@ -1,3 +1,20 @@ +/* + * Copyright 2017 Two Sigma Open Source, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once #include "axi_stream_common.hh" diff --git a/apps/axistream/tl/tl_test.cc b/apps/axistream/tl/tl_test.cc index c43add3..448e48c 100644 --- a/apps/axistream/tl/tl_test.cc +++ b/apps/axistream/tl/tl_test.cc @@ -1,4 +1,3 @@ -#define CATCH_CONFIG_MAIN #include "catch.hpp" #include "tl.hh" #include "message.hh" @@ -21,7 +20,7 @@ TEST_CASE("tl") { tl(input_stream, output_stream); - output out = reader.read(); + output out = reader.read(); reader.drain(); CHECK(out.get() == expected); From 70d76ef66e194afd89438b1908e914d88642ce54 Mon Sep 17 00:00:00 2001 From: Jacob Glueck Date: Tue, 10 Dec 2019 10:18:38 -0500 Subject: [PATCH 3/3] make mold_remover_stream example equivelent --- apps/mold_remover_stream/app.cpp | 2 +- apps/mold_remover_stream/script.tcl | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/mold_remover_stream/app.cpp b/apps/mold_remover_stream/app.cpp index 588bf02..a462bc8 100644 --- a/apps/mold_remover_stream/app.cpp +++ b/apps/mold_remover_stream/app.cpp @@ -103,7 +103,7 @@ void process_packet(ap_uint<48> macAddresst, ap_uint<32> ipAddress, int port, //std::cout << "message:" << (char)itch_message.get() << "\n"; if(itch_message.get() == 'R') { itch::directory_header m; - reader.read_rest();// + // reader.read_rest();// //reader.get(m); //auto itch_directory_message = parse_itch_directory_hdr(message); // itch_directory_message.serialize(output); diff --git a/apps/mold_remover_stream/script.tcl b/apps/mold_remover_stream/script.tcl index 7c42618..6a0b9fa 100644 --- a/apps/mold_remover_stream/script.tcl +++ b/apps/mold_remover_stream/script.tcl @@ -18,10 +18,12 @@ set_top process_packet add_files app.cpp -cflags "-I../common -I../../boost -DHLS_NO_XIL_FPO_LIB -DPLATFORM_zc702 --std=gnu++11 -Wno-unknown-attributes -Wno-multichar -D__SDSCC__ -I../mqttsn_publish_qos -DMAIN -D__SDSVHLS__ -D__SDSVHLS_SYNTHESIS__ -I../mqttsn_publish_qos/zc702-sdsoc -w" add_files -tb main.cpp -cflags "-I../common -I../../boost -I../mqttsn_publish_qos -I../mqttsn_publish_qos/zc702-sdsoc -DRAW -DPLATFORM_zc702 -std=gnu++11 -Wno-unknown-attributes -Wno-multichar -DMAIN -D__SDSVHLS__ -D__SDSVHLS_SYNTHESIS__ -w" open_solution "solution" -set_part {xc7z020clg484-1} -create_clock -period 5.000000 -name default +# set_part {xc7z020clg484-1} +# create_clock -period 5.000000 -name default +set_part {xcvu9p-flgb2104-3-e} +create_clock -period 3.103 -name default config_rtl -reset_level low -csim_design -compiler clang +# csim_design -compiler clang csynth_design -cosim_design -compiler clang -trace_level all +# cosim_design -compiler clang -trace_level all