From f33d1e25cd595a0dcabeb4201a047fb89445b0ef Mon Sep 17 00:00:00 2001 From: tjames <tom.james@cern.ch> Date: Mon, 16 Aug 2021 15:30:13 +0200 Subject: [PATCH] first MicronDAQ commit --- src/Makefile | 15 +-- src/config.h | 5 +- src/micronDAQ.cc | 264 +++++++++++++++++++++++++++++++++++++++++++++++ src/micronDAQ.h | 33 ++++++ src/scdaq.cc | 11 +- 5 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 src/micronDAQ.cc create mode 100644 src/micronDAQ.h diff --git a/src/Makefile b/src/Makefile index fb18e466..d2d7a35f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,12 +7,14 @@ # - if you need to pass flags to the linker, set LDFLAGS rather # than alter the ${TARGET}: ${OBJECTS} rule # - to see make's built-in rules use make -p - # target executable name TARGET = scdaq - +CXXFLAGS = -std=c++11 -Wall -Wextra -g -rdynamic -O3 -Wno-multichar -DLINUX -DPOSIX -I. -I${PICOBASE}/software/include/linux -I${PICOBASE}/software/include -fmessage-length=0 -fdiagnostics-show-option -fms-extensions -Wno-write-strings -DOSNAME="Linux" +#-o micronDAQ micronDAQ.h ${PICOBASE}/software/source/GString.cpp ${PICOBASE}/software/source/pico_drv.cpp ${PICOBASE}/software/source/pico_errors.cpp ${PICOBASE}/software/source/linux/linux.cpp ${PICOBASE}/software/source/pico_drv_linux.cpp +#USER_SOURCES = micronDAQ.cc +#include $(PICOBASE)/software/Makefile.common # source files -SOURCES = config.cc dma_input.cc elastico.cc FileDmaInputFilter.cc file_input.cc InputFilter.cc output.cc processor.cc scdaq.cc session.cc slice.cc WZDmaInputFilter.cc +SOURCES = micronDAQ.cc config.cc dma_input.cc elastico.cc FileDmaInputFilter.cc file_input.cc InputFilter.cc output.cc processor.cc scdaq.cc session.cc slice.cc WZDmaInputFilter.cc C_SOURCES = wz_dma.c # work out names of object files from sources @@ -23,13 +25,13 @@ OBJECTS += $(C_SOURCES:.c=.o) # appropriate rules; CXXFLAGS gets passed as part of command # invocation for both compilation (where -c is needed) and linking # (where it's not.) -CXXFLAGS = -std=c++11 -Wall -Wextra -O0 -g -rdynamic +#CXXFLAGS = -std=c++11 -Wall -Wextra -O0 -g -rdynamic #CXXFLAGS = -std=c++11 -Wall -Wextra -g -rdynamic CFLAGS = $(CXXFLAGS) LDFLAGS = -ltbb -lboost_thread -lcurl -CPPFLAGS = -I. -Iwzdma +CPPFLAGS = -I. -Iwzdma -I${PICOBASE}/software/include -I${PICOBASE}/software/include/linux -I${PICOBASE}/software/source -DLINUX -DPOSIX # default target (to build all) all: ${TARGET} @@ -57,7 +59,8 @@ ${TARGET}: ${OBJECTS} #test2.o : product.h test2.h -scdaq.o: file_input.h processor.h elastico.h output.h format.h server.h controls.h config.h session.h log.h +scdaq.o: file_input.h processor.h elastico.h output.h format.h server.h controls.h config.h session.h log.h +micronDAQ.o: micronDAQ.h ${PICOBASE}/software/source/GString.cpp ${PICOBASE}/software/include/pico_drv.h ${PICOBASE}/software/include/pico_errors.h ${PICOBASE}/software/include/linux/linux.h ${PICOBASE}/software/include/pico_drv_linux.h config.o: config.h log.h dma_input.o: dma_input.h slice.h elastico.o: elastico.h format.h slice.h controls.h log.h diff --git a/src/config.h b/src/config.h index a98724cb..ca4a5037 100644 --- a/src/config.h +++ b/src/config.h @@ -10,7 +10,7 @@ class config{ public: - enum class InputType { WZDMA, DMA, FILEDMA, FILE }; + enum class InputType { WZDMA, DMA, FILEDMA, MICRONDAQ, FILE }; config(std::string filename); @@ -27,6 +27,9 @@ public: if (input == "filedma") { return InputType::FILEDMA; } + if (input == "micronDAQ") { + return InputType::MICRONDAQ; + } if (input == "file") { return InputType::FILE; } diff --git a/src/micronDAQ.cc b/src/micronDAQ.cc new file mode 100644 index 00000000..21e33add --- /dev/null +++ b/src/micronDAQ.cc @@ -0,0 +1,264 @@ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <picodrv.h> +#include <pico_errors.h> +#include <micronDAQ.h> + + + +// add pad bytes to next multiple of 16 bytes +int micronDAQ::pad_for_16bytes(int len) { + int pad_len = len; + if(len%16 !=0) { + pad_len = len + (16 - len%16); + } + return pad_len; +} + +// print <count> 128-bit numbers from buf +void micronDAQ::print128(FILE *f, void *buf, int count) +{ + uint32_t *u32p = (uint32_t*) buf; + + for (int i=0; i < count; ++i) + fprintf(f, "0x%08x_%08x_%08x_%08x\n", u32p[4*i+3], u32p[4*i+2], u32p[4*i+1], u32p[4*i]); +} + +int micronDAQ::runMicronDAQ(int argc, char* argv[]) +{ + int err, i, j, stream1, stream2; + uint32_t *rbuf, *wbuf, u32, addr; + size_t size; + char ibuf[1024]; + PicoDrv *pico; + const char* bitFileName; + const char* load_bitfile; + // specify the .bit file name on the command line + if (argc < 2) { + fprintf(stderr, "%s: Please specify the .bit file on the command line.\n" + "For example: StreamLoopback128 ../firmware/M505_LX325T_StreamLoopback128.bit\n", argv[0]); + exit(-1); + } + bitFileName = argv[1]; + load_bitfile = argv[2]; + + printf("load", load_bitfile); + // The RunBitFile function will locate a Pico card that can run the given bit file, and is not already + // opened in exclusive-access mode by another program. It requests exclusive access to the Pico card + // so no other programs will try to reuse the card and interfere with us. + printf("Loading FPGA with '%s' ...\n", bitFileName); + // if(load_bitfile){ + // err = RunBitFile(bitFileName, &pico); + // } else{ + err = FindPico(0x852, &pico); + // } + + + + if (err < 0) { + // We use the PicoErrors_FullError function to decipher error codes from RunBitFile. + // This is more informative than just printing the numeric code, since it can report the name of a + // file that wasn't found, for example. + fprintf(stderr, "%s: RunBitFile error: %s\n", argv[0], PicoErrors_FullError(err, ibuf, sizeof(ibuf))); + exit(-1); + } + + // Data goes out to the FPGA on WriteStream ID 1 and comes back to the host on ReadStream ID 1 + // the following function call (CreateStream) opens stream ID 1 + printf("Opening streams to and from the FPGA\n"); + stream1 = pico->CreateStream(1); + stream2 = pico->CreateStream(2); + if (stream1 < 0) { + // All functions in the Pico API return an error code. If that code is < 0, then you should use + // the PicoErrors_FullError function to decode the error message. + fprintf(stderr, "%s: CreateStream error: %s\n", argv[0], PicoErrors_FullError(stream1, ibuf, sizeof(ibuf))); + // fprintf(stderr, "%s: CreateStream error: %s\n", argv[0], PicoErrors_FullError(stream2, ibuf, sizeof(ibuf))); + exit(-1); + } + if (stream2 < 0) { + // All functions in the Pico API return an error code. If that code is < 0, then you should use + // the PicoErrors_FullError function to decode the error message. + fprintf(stderr, "%s: CreateStream error: %s\n", argv[0], PicoErrors_FullError(stream2, ibuf, sizeof(ibuf))); + // fprintf(stderr, "%s: CreateStream error: %s\n", argv[0], PicoErrors_FullError(stream2, ibuf, sizeof(ibuf))); + exit(-1); + } + + // Pico streams come in two flavors: 4Byte wide, 16Byte wide (equivalent to 32bit, 128bit respectively) + // However, all calls to ReadStream and WriteStream must be 16Byte aligned (even for 4B wide streams) + // + // Now allocate 16B aligned space for read and write stream buffers + // + // Similarily, the size of the call, in bytes, must also be a multiple of 16Bytes. So check and pad + // to next 16Byte multiple + size = 1024 * sizeof(uint32_t); + //size = 16384 * sizeof(uint32_t); + size = pad_for_16bytes(size); + + if (malloc) { + err = posix_memalign((void**)&wbuf, 16, size); + if (wbuf == NULL || err) { + fprintf(stderr, "%s: posix_memalign could not allocate array of %zd bytes for write buffer\n", argv[0], size); + exit(-1); + } + err = posix_memalign((void**)&rbuf, 16, size); + if (rbuf == NULL || err) { + fprintf(stderr, "%s: posix_memalign could not allocate array of %zd bytes for read buffer\n", argv[0], size); + exit(-1); + } + } else { + printf("%s: malloc failed\n", argv[0]); + exit(-1); + } + + // fill the buffer with data we'll recognize when it comes back. + /*for (i=0; i < size/sizeof(wbuf[0]); ++i) + wbuf[i] = 0x42000000 | i; + */ + // Here is where we actually call WriteStream + // This writes "size" number of bytes from our buffer (wbuf) to the stream specified by our stream handle (e.g. 'stream') + // This call will block until it is able to write the entire "size" Bytes of data. + /*printf("Writing %zd B\n", size); + err = pico->WriteStream(stream, wbuf, size); + if (err < 0) { + fprintf(stderr, "%s: WriteStream error: %s\n", argv[0], PicoErrors_FullError(err, ibuf, sizeof(ibuf))); + exit(-1); + }*/ + + // clear our read buffer to prepare for the read. + memset(rbuf, 0, sizeof(rbuf)); + + // After we wrote some data to the FPGA, we expect the FPGA to operate upon that data and place + // some results into an output stream. + // + // As with WriteStream, a ReadStream will block until all data is returned from the FPGA to the host. + // + // A user application will either have a deterministic amount of results, or a non-deterministic amount. + // - When a non-deterministic amount of results are expected, and given the blocking nature of the ReadStream, + // a user should use the GetBytesAvailable() call to determine the amount of data available for retrieval. + // - When a deterministic amount of results is expected, a user can skip the performance impacting + // GetBytesAvailable() call and request the full amount of results. The user could also call ReadStream() + // iteratively, without GetBytesAvailable(), in which case getting smaller chunks of results may allow + // additional processing of the data on the host while the FPGA generates more results. + // Either approach works, and should be kept in mind when tuning performance of your application. + // + // In the example below we use the GetBytesAvailable() call as an example. However since the StreamLoopback + // firmware returns exactly 1 piece of output data for every 1 piece of input data that it receives this + // is not really necessary. + // + // All we have done thus far (after the call to WriteStream) is to clear out a buffer. + // However, by calling GetBytesAvailable, we will see that the FPGA has already processed some of that + // data and placed it into an output stream. + i = pico->GetBytesAvailable(stream1, true /* reading */); + j = pico->GetBytesAvailable(stream2, true /* reading */); + if (i < 0){ + fprintf(stderr, "%s: GetBytesAvailable error: %s\n", argv[0], PicoErrors_FullError(i, ibuf, sizeof(ibuf))); + exit(-1); + } + if (j < 0){ + fprintf(stderr, "%s: GetBytesAvailable error: %s\n", argv[0], PicoErrors_FullError(j, ibuf, sizeof(ibuf))); + exit(-1); + } + printf("%d Bytes are available to read from the FPGA: stream 1 .\n", i); + printf("%d Bytes are available to read from the FPGA: stream 2 .\n", j); + + // Since the StreamLoopback firmware echoes 1 piece of data back to the software for every piece of data + // that the software writes to it, we know that we can eventually read exactly the amount of data that + // was written to the FPGA. + + // Here is where we actually call ReadStream + // This reads "size" number of bytes of data from the output stream specified by our stream handle (e.g. 'stream') + // into our host buffer (rbuf) + // This call will block until it is able to read the entire "size" Bytes of data. + //size=pad_for_16bytes(pico->GetBytesAvailable(stream1, true)) - 16; + printf("Reading stream 1 %zd B\n", size); + + err = pico->ReadStream(stream2, rbuf, size); + if (err < 0) { + fprintf(stderr, "%s: ReadStream error: %s\n", argv[0], PicoErrors_FullError(err, ibuf, sizeof(ibuf))); + exit(-1); + } + + //size=pad_for_16bytes(pico->GetBytesAvailable(stream2, true)) - 16; + /*printf("Reading stream 2 %zd B\n", size); + err = pico->ReadStream(stream2, rbuf, size); + if (err < 0) { + fprintf(stderr, "%s: ReadStream error: %s\n", argv[0], PicoErrors_FullError(err, ibuf, sizeof(ibuf))); + exit(-1); + }*/ + + //i = 512; + // Now that we have received all of our read data back from the FPGA, we print out the first + // 512 bytes for the user. + // In this sample, it makes the most sense to look at this read data 16B at a time, so we print out + // 16B chunks + printf("Data received back from FPGA:\n"); + print128(stdout, rbuf, i/16); + // streams are automatically closed when the PicoDrv object is destroyed, or on program termination, but + // we can also close a stream manually. + pico->CloseStream(stream1); + pico->CloseStream(stream2); + + // verify the received data + int exp, run_total = 0; + for (i=0; i < (size / (sizeof(uint32_t))) ; i++) { + switch ( i % 4 ) { + case 0 : exp = wbuf[i] ; break ; + case 1 : run_total = run_total + wbuf[i-1] ; exp = run_total ; break ; + case 2 : exp = 0xdeadbeef ; break ; + default : exp = 0x42424242 ; break ; + } + /* if (rbuf[i] != exp) { + printf("%s: Error: unexpected values found at rbuf[%d] %x != expected %x\n", argv[0], i, rbuf[i], exp); + exit(-1); + }*/ + } + + printf("All tests successful!\n"); + + //free(wbuf); + //free(rbuf); + + exit(0); +} +/************************************************************************** + * * Entry points are here + * * Overriding virtual functions + * */ + + +//Print some additional info +void micronDAQ::print(std::ostream& out) const +{ +/* out + << ", DMA errors " << stats.nbDmaErrors + << ", oversized " << stats.nbDmaOversizedPackets + << ", resets " << stats.nbBoardResets; +*/ +} + + +// Read a packet from DMA +ssize_t micronDAQ::readInput(char **buffer, size_t bufferSize) +{ + // We need at least 1MB buffer + assert( bufferSize >= 1024*1024 ); + + //return WZDmaInputFilter::read_packet( buffer, bufferSize ); + //temporary hack + return 1; +} + + +// Notifi the DMA that packet was processed +void micronDAQ::readComplete(char *buffer) { +/* (void)(buffer); + + // Free the DMA buffer + if ( wz_read_complete( &dma_ ) < 0 ) { + throw std::system_error(errno, std::system_category(), "Cannot complete WZ DMA read"); + } +*/ +} diff --git a/src/micronDAQ.h b/src/micronDAQ.h new file mode 100644 index 00000000..880f7b60 --- /dev/null +++ b/src/micronDAQ.h @@ -0,0 +1,33 @@ +#ifndef MICRONDAQ_H +#define MICRONDAQ_H + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <pico_drv.h> +#include <pico_errors.h> +#include <stdint.h> +#include <memory> +#include <InputFilter.h> +#include <WZDmaInputFilter.h> +#include <cassert> +class micronDAQ : public InputFilter { + public: + micronDAQ(); + virtual ~micronDAQ(); + + int pad_for_16bytes(int); + void print128(FILE*, void*, int); + int runMicronDAQ(int, char**); + private: + + protected: + ssize_t readInput(char **buffer, size_t bufferSize); // Override + void readComplete(char *buffer); // Override + void print(std::ostream& out) const; // Override +}; +typedef std::shared_ptr<micronDAQ> micronDAQPtr; + + +#endif diff --git a/src/scdaq.cc b/src/scdaq.cc index 8207e6f1..5baf15cd 100644 --- a/src/scdaq.cc +++ b/src/scdaq.cc @@ -18,6 +18,7 @@ #include "InputFilter.h" #include "FileDmaInputFilter.h" #include "WZDmaInputFilter.h" +#include "micronDAQ.h" #include "dma_input.h" #include "file_input.h" #include "processor.h" @@ -28,6 +29,7 @@ #include "controls.h" #include "config.h" #include "log.h" +#include "micronDAQ.h" using namespace std; @@ -70,9 +72,14 @@ int run_pipeline( int nbThreads, ctrl& control, config& conf ) input_filter = std::make_shared<FileDmaInputFilter>( conf.getInputFile(), MAX_BYTES_PER_INPUT_SLICE, TOTAL_SLICES, control ); } else if (input == config::InputType::WZDMA ) { - // Create WZ DMA reader + // create wz dma reader input_filter = std::make_shared<WZDmaInputFilter>( MAX_BYTES_PER_INPUT_SLICE, TOTAL_SLICES, control ); - + + } else if (input == config::InputType::MICRONDAQ ) { + // create micronDAQ reader + //input_filter = std::make_shared<micronDAQ>(); + throw std::runtime_error("input type micronDAQ is a work in progress"); + } else { throw std::invalid_argument("Configuration error: Unknown input type was specified"); } -- GitLab