From a41fe1784d4f334fd8d2c3e0771e87a323f645fa Mon Sep 17 00:00:00 2001
From: Jonathan Hanks <jonathan.hanks@ligo.org>
Date: Fri, 23 Aug 2019 14:32:33 -0700
Subject: [PATCH] Created a 'multi' fe stream generator.

fe_stream_test is a single front end outputing a rmIpcStr to mbuf.

fe_multi_stream_test can be used to simulate fe systems in bulk, it
outputs to a dac_multi_cycle_t in mbuf, along with populating a
ini directory and master file.  This is targeted for daqd development
and testing.

This adds a fe_generator_support library which holds some common code
between the two generators.

This also adds another check to fe_stream_check, verifying the data crc.
---
 src/fe_stream_test/CMakeLists.txt          |  27 +-
 src/fe_stream_test/fe_generator_support.cc | 111 +++++
 src/fe_stream_test/fe_generator_support.hh |  55 +++
 src/fe_stream_test/fe_multi_stream_test.cc | 506 +++++++++++++++++++++
 src/fe_stream_test/fe_stream_check.cc      |  19 +
 src/fe_stream_test/fe_stream_generator.cc  |  63 +++
 src/fe_stream_test/fe_stream_generator.hh  |  59 +--
 src/fe_stream_test/fe_stream_test.cc       |  93 +---
 8 files changed, 775 insertions(+), 158 deletions(-)
 create mode 100644 src/fe_stream_test/fe_generator_support.cc
 create mode 100644 src/fe_stream_test/fe_generator_support.hh
 create mode 100644 src/fe_stream_test/fe_multi_stream_test.cc
 create mode 100644 src/fe_stream_test/fe_stream_generator.cc

diff --git a/src/fe_stream_test/CMakeLists.txt b/src/fe_stream_test/CMakeLists.txt
index ccbe6c8de..03c7e6c5d 100644
--- a/src/fe_stream_test/CMakeLists.txt
+++ b/src/fe_stream_test/CMakeLists.txt
@@ -1,9 +1,14 @@
 add_library(fe_stream_generator STATIC
+        fe_stream_generator.cc
         fe_stream_generator.hh
         str_split.cc
         str_split.hh)
 target_include_directories(fe_stream_generator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
 
+add_library(fe_generator_support STATIC
+        fe_generator_support.hh
+        fe_generator_support.cc)
+
 add_executable(fe_stream_test
         fe_stream_test.cc
         gps.hh
@@ -11,8 +16,19 @@ add_executable(fe_stream_test
 target_link_libraries(fe_stream_test
         PRIVATE
         fe_stream_generator
+        fe_generator_support
+        rt)
+
+add_executable(fe_multi_stream_test
+        fe_multi_stream_test.cc
+        gps.hh)
+target_link_libraries(fe_multi_stream_test
+        PRIVATE
+        fe_stream_generator
+        fe_generator_support
         rt)
 
+
 add_executable(fe_stream_check
         fe_stream_check.cc
         )
@@ -28,14 +44,3 @@ target_link_libraries(fe_check
         rt
         driver::shmem)
 
-if (${libNDS2Client_FOUND})
-
-#    add_executable(fe_stream_check
-#            fe_stream_check.cc
-#            )
-#    target_link_libraries(fe_stream_check
-#            PRIVATE
-#            fe_stream_generator
-#            nds2client::cxx)
-
-endif (${libNDS2Client_FOUND})
diff --git a/src/fe_stream_test/fe_generator_support.cc b/src/fe_stream_test/fe_generator_support.cc
new file mode 100644
index 000000000..ef427dbcd
--- /dev/null
+++ b/src/fe_stream_test/fe_generator_support.cc
@@ -0,0 +1,111 @@
+//
+// Created by jonathan.hanks on 8/22/19.
+//
+#include <fstream>
+
+#include "fe_generator_support.hh"
+
+extern "C" {
+
+#include "../drv/crc.c"
+}
+
+std::string
+cleaned_system_name( const std::string& system_name )
+{
+    std::vector< char > buf;
+    for ( int i = 0; i < system_name.size( ); ++i )
+    {
+        if ( system_name[ i ] == ':' )
+            continue;
+        buf.push_back( (char)tolower( system_name[ i ] ) );
+    }
+    buf.push_back( '\0' );
+    return std::string( buf.data( ) );
+}
+
+std::string
+generate_ini_filename( const std::string& ini_dir,
+                       const std::string& system_name )
+{
+    std::ostringstream ss;
+    ss << ini_dir << "/" << system_name << ".ini";
+    return ss.str( );
+}
+
+std::string
+generate_par_filename( const std::string& ini_dir,
+                       const std::string& system_name )
+{
+    std::ostringstream ss;
+    ss << ini_dir << "/tpchn_" << system_name << ".par";
+    return ss.str( );
+}
+
+void
+output_ini_files( const std::string&          ini_dir,
+                  const std::string&          system_name,
+                  std::vector< GeneratorPtr > channels,
+                  std::vector< GeneratorPtr > tp_channels,
+                  int                         dcuid,
+                  int                         model_rate)
+{
+    using namespace std;
+
+    string   clean_name = cleaned_system_name( system_name );
+    string   fname_ini = generate_ini_filename( ini_dir, clean_name );
+    string   fname_par = generate_par_filename( ini_dir, clean_name );
+    ofstream os_ini( fname_ini.c_str( ) );
+    ofstream os_par( fname_par.c_str( ) );
+    os_ini << "[default]\ngain=1.0\nacquire=3\ndcuid=" << dcuid
+           << "\nifoid=0\n";
+    os_ini << "datatype=2\ndatarate=" << model_rate << "\noffset=0\nslope=1.0\nunits=undef\n\n";
+
+    vector< GeneratorPtr >::iterator cur = channels.begin( );
+    for ( ; cur != channels.end( ); ++cur )
+    {
+        Generator* gen = ( cur->get( ) );
+        ( *cur )->output_ini_entry( os_ini );
+    }
+
+    for ( cur = tp_channels.begin( ); cur != tp_channels.end( ); ++cur )
+    {
+        Generator* gen = ( cur->get( ) );
+        ( *cur )->output_par_entry( os_par );
+    }
+}
+
+unsigned int
+calculate_ini_crc( const std::string& ini_dir, const std::string& system_name )
+{
+    std::string fname_ini =
+        generate_ini_filename( ini_dir, cleaned_system_name( system_name ) );
+    std::ifstream is( fname_ini.c_str( ), std::ios::binary );
+
+    std::vector< char > buffer( 64 * 1024 );
+
+    size_t       file_size = 0;
+    unsigned int file_crc = 0;
+    while ( is.read( &buffer[ 0 ], buffer.size( ) ) )
+    {
+        file_crc = crc_ptr( &buffer[ 0 ], buffer.size( ), file_crc );
+        file_size += buffer.size( );
+    }
+    if ( is.gcount( ) > 0 )
+    {
+        file_crc = crc_ptr( &buffer[ 0 ], is.gcount( ), file_crc );
+        file_size += is.gcount( );
+    }
+    file_crc = crc_len( file_size, file_crc );
+    return file_crc;
+}
+
+unsigned int
+calculate_crc( const void* buffer, size_t len )
+{
+    if (!buffer || len <= 0)
+    {
+        return 0;
+    }
+    return crc_len(len, crc_ptr( reinterpret_cast<char*>(const_cast<void*>(buffer)), len, 0));
+}
\ No newline at end of file
diff --git a/src/fe_stream_test/fe_generator_support.hh b/src/fe_stream_test/fe_generator_support.hh
new file mode 100644
index 000000000..9532dffc8
--- /dev/null
+++ b/src/fe_stream_test/fe_generator_support.hh
@@ -0,0 +1,55 @@
+//
+// Created by jonathan.hanks on 8/22/19.
+//
+
+#ifndef DAQD_TRUNK_FE_GENERATOR_SUPPORT_HH
+#define DAQD_TRUNK_FE_GENERATOR_SUPPORT_HH
+
+#include <string>
+#include <vector>
+
+#include "fe_stream_generator.hh"
+
+class ChNumDb
+{
+private:
+    int max_;
+
+public:
+    ChNumDb( ) : max_( 40000 )
+    {
+    }
+    explicit ChNumDb( int start ) : max_( start )
+    {
+    }
+
+    int
+    next( int channel_type )
+    {
+        max_++;
+        return max_;
+    }
+};
+
+std::string cleaned_system_name( const std::string& system_name );
+
+std::string generate_ini_filename( const std::string& ini_dir,
+                                   const std::string& system_name );
+
+std::string generate_par_filename( const std::string& ini_dir,
+                                   const std::string& system_name );
+
+void output_ini_files( const std::string&          ini_dir,
+                       const std::string&          system_name,
+                       std::vector< GeneratorPtr > channels,
+                       std::vector< GeneratorPtr > tp_channels,
+                       int                         dcuid,
+                       int                         model_rate=2048);
+
+unsigned int calculate_ini_crc( const std::string& ini_dir,
+                                const std::string& system_name );
+
+unsigned int
+calculate_crc( const void* buffer, size_t len );
+
+#endif // DAQD_TRUNK_FE_GENERATOR_SUPPORT_HH
diff --git a/src/fe_stream_test/fe_multi_stream_test.cc b/src/fe_stream_test/fe_multi_stream_test.cc
new file mode 100644
index 000000000..5739c27a8
--- /dev/null
+++ b/src/fe_stream_test/fe_multi_stream_test.cc
@@ -0,0 +1,506 @@
+//
+// Created by jonathan.hanks on 8/22/19.
+//
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <numeric>
+#include <map>
+#include <string>
+#include <sstream>
+
+#include "fe_stream_generator.hh"
+#include "fe_generator_support.hh"
+#include "../include/daq_core.h"
+
+#include "gps.hh"
+
+extern "C" {
+
+#include "../drv/rfm.c"
+}
+
+struct ModelParams
+{
+    ModelParams(): name(), model_rate(2048), dcuid(-1), data_rate(10000) {}
+    std::string name;
+    int model_rate;
+    int dcuid;
+    int data_rate;
+};
+
+struct Options
+{
+    Options(): models(), ini_root(), master_path("master"), mbuf_name("local_dc"),mbuf_size_mb(100), show_help(false) {}
+    std::vector<ModelParams> models;
+    std::string ini_root;
+    std::string master_path;
+    std::string mbuf_name;
+    size_t mbuf_size_mb;
+    bool show_help;
+};
+
+class GeneratorStore
+{
+public:
+    GeneratorStore( ) : store_( )
+    {
+    }
+
+    GeneratorPtr
+    get( const std::string& name )
+    {
+        std::map<std::string, GeneratorPtr>::iterator it = store_.find( name );
+        return ( it == store_.end() ? GeneratorPtr((Generator*)0) : it->second);
+    }
+
+    void
+    set( const std::string& name, GeneratorPtr& g )
+    {
+        std::map<std::string, GeneratorPtr>::iterator it = store_.find( name );
+        if (it == store_.end())
+        {
+            store_.insert(std::make_pair(name, g));
+        }
+    }
+
+private:
+    std::map< std::string, GeneratorPtr > store_;
+};
+
+class IniManager
+{
+public:
+    IniManager(const std::string& ini_root, const std::string& master_path):
+    ini_root_(ini_root),
+    master_path_(master_path)
+    {
+    }
+
+    int
+    add(const std::string& model_name, int dcu_id, int model_rate, std::vector<GeneratorPtr> chans,
+        std::vector<GeneratorPtr> test_points)
+    {
+        output_ini_files(ini_root_, model_name, chans, test_points, dcu_id, model_rate);
+        add_to_master(model_name);
+        return calculate_ini_crc(ini_root_, model_name);
+    }
+
+private:
+    void
+    add_to_master(const std::string& model_name)
+    {
+        std::string name = cleaned_system_name(model_name);
+        master_contents_.push_back(generate_ini_filename(ini_root_, model_name));
+        master_contents_.push_back(generate_par_filename(ini_root_, model_name));
+
+        rewrite_master();
+    }
+
+    void
+    rewrite_master()
+    {
+        std::string tmp_name = master_path_;
+        tmp_name += ".tmp";
+
+        {
+            std::ofstream out(tmp_name.c_str());
+            std::ostream_iterator<std::string> it(out, "\n");
+            std::copy(master_contents_.begin(), master_contents_.end(), it);
+        }
+
+        if (std::rename(tmp_name.c_str(), master_path_.c_str()) == -1)
+        {
+            throw std::runtime_error("Unable to replace the master file");
+        }
+
+    }
+
+    std::vector<std::string> master_contents_;
+
+    std::string ini_root_;
+    std::string master_path_;
+};
+
+/**
+ * @brief Represent a model (essentially a list of channels + testpoints)
+ */
+class Model
+{
+public:
+    /**
+     * @brief Initialize a model, generating a set of channels
+     * @param name model name
+     * @param dcu_id dcu
+     * @param model_rate rate
+     * @param data_rate_bytes The max number of bytes to use.  Should be a multiple
+     * of 4 (sizeof float)
+     */
+    Model( IniManager& ini_manager, const std::string& name, int dcu_id, int model_rate, int data_rate_bytes):
+    name_(name), dcu_id_(dcu_id), model_rate_(model_rate),
+    data_rate_bytes_(data_rate_bytes),
+    config_crc_(0),
+    tp_table_(32, 0),
+    generators_(),
+    tp_generators_(),
+    null_tp_((Generator*)0)
+    {
+        size_t fast_data_bytes = data_rate_bytes_/2;
+        size_t mid_data_bytes = data_rate_bytes/4;
+        size_t slow_data_bytes = data_rate_bytes/4;
+
+        if (fast_data_bytes + mid_data_bytes + slow_data_bytes != data_rate_bytes_)
+        {
+            std::cerr << fast_data_bytes << "\n";
+            std::cerr << mid_data_bytes << "\n";
+            std::cerr << slow_data_bytes << "\n";
+            std::cerr << data_rate_bytes_ << "\n";
+            throw std::runtime_error("Fast/mid/slow mix is bad, rates do not add up");
+        }
+
+        int mid_rate = model_rate_/2;
+
+        size_t fast_channel_num = fast_data_bytes/(sizeof(float)*model_rate_);
+        size_t mid_channel_num = mid_data_bytes/(sizeof(float)*mid_rate);
+        size_t slow_channel_num = slow_data_bytes/(sizeof(float)*16);
+        size_t channel_num = fast_channel_num + slow_channel_num;
+
+        generators_.reserve(channel_num);
+        tp_generators_.reserve(tp_table_.size());
+
+        ChNumDb chDb;
+        ChNumDb tpDb;
+
+        for (size_t i = 0; i < fast_channel_num; ++i)
+        {
+            int chnum = chDb.next(4);
+
+            std::ostringstream ss;
+            ss << name_ << "-" << i;
+            generators_.push_back(GeneratorPtr(new Generators::GPSSecondWithOffset<int>(SimChannel(ss.str(), 2, model_rate_, chnum), (i + dcu_id_)%21)));
+        }
+        for (size_t i = fast_channel_num; i < mid_channel_num; ++i)
+        {
+            int chnum = chDb.next(4);
+
+            std::ostringstream ss;
+            ss << name_ << "-" << i;
+            generators_.push_back(GeneratorPtr(new Generators::GPSSecondWithOffset<int>(SimChannel(ss.str(), 2, mid_rate, chnum), (i + dcu_id_)%21)));
+        }
+        for (size_t i = mid_channel_num; i < channel_num; ++i)
+        {
+            int chnum = chDb.next(4);
+
+            std::ostringstream ss;
+            ss << name_ << "-" << i;
+            generators_.push_back(GeneratorPtr(new Generators::GPSSecondWithOffset<int>(SimChannel(ss.str(), 2, 16, chnum), (i + dcu_id_)%21)));
+        }
+
+
+        for (size_t i = 0; i < tp_table_.size(); ++i)
+        {
+            int chnum = tpDb.next(4);
+
+            std::ostringstream ss;
+            ss << name_ << "-TP" << i;
+            // TP need truncated
+            tp_generators_.push_back(GeneratorPtr(new Generators::GPSMod100kSecWithOffset<float>(SimChannel(ss.str(), 4, model_rate_, chnum, dcu_id_), (i + dcu_id_)%21)));
+        }
+        GeneratorPtr null_tp = GeneratorPtr(new Generators::StaticValue<float>(SimChannel("null_tp_value", 4, model_rate_, 0x7fffffff), 0.0));
+        config_crc_ = ini_manager.add(name, dcu_id, model_rate, generators_, tp_generators_);
+    }
+
+    const std::string&
+    name() const
+    {
+        return name_;
+    }
+
+    int
+    dcu_id() const
+    {
+        return dcu_id_;
+    }
+
+    size_t
+    model_rate() const
+    {
+        return model_rate_;
+    }
+
+    size_t
+    chan_count() const
+    {
+        return generators_.size();
+    }
+
+    std::vector<GeneratorPtr>&
+    generators()
+    {
+        return generators_;
+    }
+
+    std::vector<GeneratorPtr>&
+    tp_generators()
+    {
+        return tp_generators_;
+    }
+
+    int
+    config_crc() const
+    {
+        return config_crc_;
+    }
+private:
+    std::string name_;
+    int                         dcu_id_;
+    size_t                      model_rate_;
+    size_t                      data_rate_bytes_;
+    int                         config_crc_;
+    std::vector< int >          tp_table_;
+    std::vector< GeneratorPtr > generators_;
+    std::vector< GeneratorPtr > tp_generators_;
+    GeneratorPtr                null_tp_;
+};
+
+typedef std::tr1::shared_ptr<Model> ModelPtr;
+
+/**
+ * @brief Given a set of models, and a data block, fill the data block with the models data.
+ * @param models Set of models
+ * @param dest Where to put the data
+ * @param max_data_size The data block size
+ * @param cycle cycle number
+ * @param cur_time gps time to generate data for
+ */
+void
+generate_models(std::vector<ModelPtr>& models, daq_dc_data_t& dest, size_t max_data_size, int cycle, const GPS::gps_time& cur_time )
+{
+    dest.header.dcuTotalModels = models.size();
+    dest.header.fullDataBlockSize = 0;
+    char* data = dest.dataBlock;
+    char* data_end = data+max_data_size;
+    for (int i = 0; i < models.size(); ++i)
+    {
+        char *start = data;
+        ModelPtr& mp = models[i];
+        daq_msg_header_t& dcu_header = dest.header.dcuheader[i];
+        dcu_header.dcuId = mp->dcu_id();
+        dcu_header.fileCrc = mp->config_crc();
+        dcu_header.status = 2;
+        dcu_header.cycle = cycle;
+        dcu_header.timeSec = cur_time.sec;
+        dcu_header.timeNSec = cur_time.nanosec;
+        dcu_header.dataCrc = 0;
+        dcu_header.dataBlockSize = 0;
+        dcu_header.tpBlockSize = 0;
+        dcu_header.tpCount = 0;
+        std::fill(&dcu_header.tpNum[0], &dcu_header.tpNum[DAQ_GDS_MAX_TP_NUM], 0);
+
+        std::vector<GeneratorPtr>& gen = mp->generators();
+        std::vector<GeneratorPtr>::iterator it = gen.begin();
+        for (; it != gen.end(); ++it )
+        {
+            char* next_data = (*it)->generate(static_cast<int>(cur_time.sec), static_cast<int>(cur_time.nanosec), data);
+            dest.header.fullDataBlockSize += static_cast<int>(next_data - data);
+            data = next_data;
+        }
+        dcu_header.dataBlockSize = static_cast<unsigned int>(data - start);
+        dcu_header.dataCrc = calculate_crc( reinterpret_cast<void*>(start), dcu_header.dataBlockSize);
+    }
+}
+
+void
+usage(const char* progname)
+{
+    std::cout << progname << " simulation for multiple LIGO FE computers\n";
+    std::cout << "\nUsage: " << progname << " options\n";
+    std::cout << "Where options are:\n";
+    std::cout << "\t-i path - Path to the ini/par file folder to use (should be a full path)\n";
+    std::cout << "\t-M path - Path to the master file (will be overwritten) [master]\n";
+    std::cout << "\t-b name - Name of the output mbuf [local_dc]\n";
+    std::cout << "\t-m size_mb - Size in MB of the output mbuf [100]\n";
+    std::cout << "\t-k size_kb - Default data rate of each model in kB\n";
+    std::cout << "\t-R num - number of models to simulate [1-120]\n";
+    std::cout << "\t-h - this help\n";
+}
+
+Options
+parse_arguments(int argc, char* argv[])
+{
+    Options opts;
+    int c;
+    int model_data_size = 700*1024;
+
+    while ((c = getopt(argc, argv, "i:M:b:m:R:k:h")) != -1)
+    {
+        switch (c)
+        {
+        case 'i':
+            opts.ini_root = optarg;
+            break;
+        case 'M':
+            opts.master_path = optarg;
+            break;
+        case 'b':
+            opts.mbuf_name = optarg;
+            break;
+        case 'm':
+            opts.mbuf_size_mb = std::atoi(optarg);
+            break;
+        case 'k':
+            model_data_size = std::atoi(optarg);
+            if (model_data_size < 10 || model_data_size > 3900)
+            {
+                throw std::runtime_error("Invalid model_data_size [10-3900]k");
+            }
+            model_data_size *= 1024;
+            break;
+        case 'R':
+        {
+            int count = std::atoi(optarg);
+            if (count > 120 || count < 1)
+            {
+                throw std::runtime_error("Must specify [1-128] models");
+            }
+            if (!opts.models.empty())
+            {
+                throw std::runtime_error("Models already specified");
+            }
+            if (model_data_size*count >= (opts.mbuf_size_mb*1024*1024))
+            {
+                throw std::runtime_error("Too much data, increase the buffer size or reduce models or data rate");
+            }
+            opts.models.reserve(count);
+            int dcuid = 5;
+            for (int i = 0; i < count; ++i)
+            {
+                ModelParams params;
+                params.dcuid = dcuid;
+                params.model_rate = 2048;
+                params.data_rate = model_data_size;
+                std::ostringstream os;
+                os << "mod" << dcuid;
+                params.name = os.str();
+                opts.models.push_back(params);
+                ++dcuid;
+            }
+        }
+        break;
+        case 'h':
+        default:
+            opts.show_help = true;
+            break;
+        }
+    }
+    return opts;
+}
+
+
+/**
+ * @brief helper function to sum models channel count via std::accumulate
+ * @param current current value
+ * @param m model ptr
+ * @return current + m->chan_count
+ */
+size_t
+sum_channels(const size_t current, const ModelPtr& m)
+{
+    return current + m->chan_count();
+}
+
+int
+main(int argc, char* argv[])
+{
+    Options opts = parse_arguments(argc, argv);
+    if (opts.show_help)
+    {
+        usage(argv[0]);
+        exit(1);
+    }
+    std::vector<ModelPtr> models;
+    IniManager ini_manager(opts.ini_root, opts.master_path);
+
+    models.reserve(opts.models.size());
+    for (int i = 0; i < opts.models.size(); ++i)
+    {
+        ModelParams& param = opts.models[i];
+        models.push_back(ModelPtr(new Model(ini_manager, param.name, param.dcuid, param.model_rate, param.data_rate)));
+    }
+
+    size_t total_chans = std::accumulate(models.begin(), models.end(), (size_t)0, sum_channels);
+    std::cout << "Model count: " << models.size() << "\n";
+    std::cout << "Channel count: " << total_chans << "\n";
+
+    std::cout << "Sizeof daq_multi_cycle_data_t: " << sizeof(daq_multi_cycle_data_t) << "\n";
+    std::cout << "Sizeof daq_multi_cycle_data_t: " << sizeof(daq_multi_cycle_data_t)/(1024*1024) << "MB\n";
+    std::cout << "Sizeof daq_multi_cycle_data_t data: " << DAQ_TRANSIT_DC_DATA_BLOCK_SIZE * DAQ_NUM_DATA_BLOCKS_PER_SECOND << "\n";
+    std::cout << "Sizeof daq_multi_cycle_data_t data: " << (DAQ_TRANSIT_DC_DATA_BLOCK_SIZE * DAQ_NUM_DATA_BLOCKS_PER_SECOND)/(1024*1024) << "MB\n";
+
+    std::vector<daq_dc_data_t> buffer(1);
+
+    volatile char *shmem = 0;
+
+    std::string shmem_sysname = opts.mbuf_name;
+
+    size_t buffer_size_mb = opts.mbuf_size_mb;
+    size_t buffer_size = buffer_size_mb*1024*1024;
+    shmem = reinterpret_cast<volatile char*>(findSharedMemorySize(const_cast<char*>(shmem_sysname.c_str()), buffer_size_mb));
+    if (shmem == 0)
+    {
+        std::cerr << "Unable to open shmem buffer\n";
+        exit(1);
+    }
+    volatile daq_multi_cycle_header_t* ifo_header = reinterpret_cast<volatile daq_multi_cycle_header_t*>(shmem);
+    ifo_header->maxCycle = 16;
+    size_t data_size = buffer_size - sizeof(*ifo_header);
+    data_size /= 16;
+    data_size -= (data_size % 8);
+    ifo_header->cycleDataSize = static_cast<unsigned int>(data_size);
+    volatile char* ifo_data = shmem + sizeof(*ifo_header);
+
+    GPS::gps_clock clock(0);
+
+    GPS::gps_time time_step = GPS::gps_time(0, 1000000000/16);
+    GPS::gps_time transmit_time = clock.now();
+    ++transmit_time.sec;
+    transmit_time.nanosec = 0;
+
+    int delay_multiplier = 0;
+    int cycle = 0;
+
+    while (true)
+    {
+        // The simulation writes 1/16s behind real time
+        // So we wait until the cycle start, then compute
+        // then write and wait for the next cycle.
+        GPS::gps_time now = clock.now();
+        while (now < transmit_time)
+        {
+            usleep(1);
+            now = clock.now();
+        }
+        usleep(delay_multiplier * 1000);
+
+        generate_models(models, buffer.front(), sizeof(buffer.front().dataBlock), cycle, transmit_time);
+
+        volatile char* dest = ifo_data + (cycle*data_size);
+        char *start = reinterpret_cast<char*>(&buffer.front());
+        std::copy(start, start+sizeof(daq_dc_data_t), dest);
+
+        ifo_header->curCycle = cycle;
+
+        if (cycle == 0)
+        {
+            GPS::gps_time end = clock.now();
+            GPS::gps_time delta = end - now;
+            std::cout << "Cycle took " << static_cast<double>(delta.nanosec)/1000000000.0 << "s\n";
+        }
+
+        cycle = (cycle + 1) % 16;
+        transmit_time = transmit_time + time_step;
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/src/fe_stream_test/fe_stream_check.cc b/src/fe_stream_test/fe_stream_check.cc
index a80f90112..1184b8ee2 100644
--- a/src/fe_stream_test/fe_stream_check.cc
+++ b/src/fe_stream_test/fe_stream_check.cc
@@ -22,6 +22,11 @@
 
 #include "daq_core.h"
 
+extern "C" {
+
+#include "../drv/crc.c"
+}
+
 /*!
  * @brief Generic buffer typedef
  */
@@ -506,6 +511,7 @@ public:
         std::vector<char> tmp(model.meta_data.rate*8);
 
         const char* data = &(slice_header->dataBlock[dcu_data_offset]);
+        char* data_start = const_cast<char*>(data);
         for (int i = 0; i < model.channels.size(); ++i)
         {
             std::fill(tmp.begin(), tmp.end(), 1);
@@ -523,6 +529,15 @@ public:
 
             data += gen_size;
         }
+
+        int dcu_data_size = static_cast<int>(data - data_start);
+        unsigned int data_crc = crc_len(dcu_data_size, crc_ptr(data_start, dcu_data_size, 0));
+        if (data_crc != dcu_header->dataCrc)
+        {
+            std::cerr << "CRC mismatch on dcu id " << dcu_header->dcuId << "\n";
+            throw std::runtime_error("CRC Mismatch");
+        }
+
         for (int i = 0; i < dcu_header->tpCount; ++i)
         {
             int expected_len = (model.meta_data.rate * sizeof(float))/16;
@@ -623,5 +638,9 @@ main(int argc, char* argv[])
 
     CheckDCUData check_dcu_data(opts, header);
     std::for_each( models.begin(), models.end(), check_dcu_data);
+    if (opts.verbose)
+    {
+        std::cout << "Tests passed\n";
+    }
     return 0;
 }
\ No newline at end of file
diff --git a/src/fe_stream_test/fe_stream_generator.cc b/src/fe_stream_test/fe_stream_generator.cc
new file mode 100644
index 000000000..e0a22f12a
--- /dev/null
+++ b/src/fe_stream_test/fe_stream_generator.cc
@@ -0,0 +1,63 @@
+//
+// Created by jonathan.hanks on 8/22/19.
+//
+#include "fe_stream_generator.hh"
+
+GeneratorPtr
+create_generator(const std::string& generator, const SimChannel& ch)
+{
+  if (ch.data_type() != 2)
+    throw std::runtime_error("Invalid/unsupported data type for a generator");
+  if (generator == "gps_sec") {
+    return GeneratorPtr(new Generators::GPSSecondGenerator(ch));
+  }
+  throw std::runtime_error("Unknown generator type");
+}
+
+GeneratorPtr
+create_generator(const std::string& channel_name)
+{
+  std::vector<std::string> parts = split(channel_name, "--");
+  if (parts.size() < 4)
+    throw std::runtime_error("Generator name has too few parts, invalid input");
+  int data_type = 0;
+  {
+    std::istringstream is (parts[parts.size()-2]);
+    is >> data_type;
+  }
+  int rate = 0;
+  {
+    std::istringstream is (parts[parts.size()-1]);
+    is >> rate;
+  }
+  if (!(data_type == 2 || data_type == 4) || rate < 16)
+    throw std::runtime_error("Invalid data type or rate found");
+  std::string& base = parts[0];
+  std::string& name = parts[1];
+  int arg_count = parts.size()-4; // ignore base channel name, data type, rate
+  if (name == "gpssoff1p" && arg_count == 1)
+  {
+    std::istringstream is(parts[2]);
+    int offset = 0;
+    is >> offset;
+    if (data_type == 2)
+      return GeneratorPtr(new Generators::GPSSecondWithOffset<int>(SimChannel(base, data_type, rate, 0), offset));
+    else
+      return GeneratorPtr(new Generators::GPSSecondWithOffset<float>(SimChannel(base, data_type, rate, 0), offset));
+
+  }
+  else if (name == "gpssmd100koff1p" && arg_count == 1)
+  {
+    std::istringstream is(parts[2]);
+    int offset = 0;
+    is >> offset;
+    if (data_type == 2)
+      return GeneratorPtr(new Generators::GPSMod100kSecWithOffset<int>(SimChannel(base, data_type, rate, 0), offset));
+    else
+      return GeneratorPtr(new Generators::GPSMod100kSecWithOffset<float>(SimChannel(base, data_type, rate, 0), offset));
+  }
+  else
+  {
+    throw std::runtime_error("Unknown generator type");
+  }
+}
diff --git a/src/fe_stream_test/fe_stream_generator.hh b/src/fe_stream_test/fe_stream_generator.hh
index b58ee22c2..1c8bf4453 100644
--- a/src/fe_stream_test/fe_stream_generator.hh
+++ b/src/fe_stream_test/fe_stream_generator.hh
@@ -232,61 +232,10 @@ namespace Generators {
     };
 }
 
-GeneratorPtr create_generator(const std::string& generator, const SimChannel& ch)
-{
-    if (ch.data_type() != 2)
-        throw std::runtime_error("Invalid/unsupported data type for a generator");
-    if (generator == "gps_sec") {
-        return GeneratorPtr(new Generators::GPSSecondGenerator(ch));
-    }
-    throw std::runtime_error("Unknown generator type");
-}
-
-GeneratorPtr create_generator(const std::string& channel_name)
-{
-    std::vector<std::string> parts = split(channel_name, "--");
-    if (parts.size() < 4)
-        throw std::runtime_error("Generator name has too few parts, invalid input");
-    int data_type = 0;
-    {
-        std::istringstream is (parts[parts.size()-2]);
-        is >> data_type;
-    }
-    int rate = 0;
-    {
-        std::istringstream is (parts[parts.size()-1]);
-        is >> rate;
-    }
-    if (!(data_type == 2 || data_type == 4) || rate < 16)
-        throw std::runtime_error("Invalid data type or rate found");
-    std::string& base = parts[0];
-    std::string& name = parts[1];
-    int arg_count = parts.size()-4; // ignore base channel name, data type, rate
-    if (name == "gpssoff1p" && arg_count == 1)
-    {
-        std::istringstream is(parts[2]);
-        int offset = 0;
-        is >> offset;
-        if (data_type == 2)
-            return GeneratorPtr(new Generators::GPSSecondWithOffset<int>(SimChannel(base, data_type, rate, 0), offset));
-        else
-            return GeneratorPtr(new Generators::GPSSecondWithOffset<float>(SimChannel(base, data_type, rate, 0), offset));
+GeneratorPtr
+create_generator(const std::string& generator, const SimChannel& ch);
 
-    }
-    else if (name == "gpssmd100koff1p" && arg_count == 1)
-    {
-        std::istringstream is(parts[2]);
-        int offset = 0;
-        is >> offset;
-        if (data_type == 2)
-            return GeneratorPtr(new Generators::GPSMod100kSecWithOffset<int>(SimChannel(base, data_type, rate, 0), offset));
-        else
-            return GeneratorPtr(new Generators::GPSMod100kSecWithOffset<float>(SimChannel(base, data_type, rate, 0), offset));
-    }
-    else
-    {
-        throw std::runtime_error("Unknown generator type");
-    }
-}
+GeneratorPtr
+create_generator(const std::string& channel_name);
 
 #endif //DAQD_FE_STREAM_GENERATOR_HH
diff --git a/src/fe_stream_test/fe_stream_test.cc b/src/fe_stream_test/fe_stream_test.cc
index 8848f2c39..60f9d618b 100644
--- a/src/fe_stream_test/fe_stream_test.cc
+++ b/src/fe_stream_test/fe_stream_test.cc
@@ -3,7 +3,6 @@
 //
 
 #include <algorithm>
-#include <fstream>
 #include <iostream>
 #include <sstream>
 #include <string>
@@ -16,6 +15,7 @@
 #include "../include/daq_core.h"
 
 #include "fe_stream_generator.hh"
+#include "fe_generator_support.hh"
 
 #include "gps.hh"
 
@@ -25,99 +25,8 @@ extern "C" {
 
 #include "../drv/rfm.c"
 
-#include "../drv/crc.c"
-
-}
-
-
-class ChNumDb
-{
-private:
-    int max_;
-public:
-    ChNumDb(): max_(40000) {}
-    explicit ChNumDb(int start): max_(start) {}
-
-    int next(int channel_type) { max_++; return max_; }
-};
-
-std::string cleaned_system_name(const std::string& system_name)
-{
-    std::vector<char> buf;
-    for (int i = 0; i < system_name.size(); ++i)
-    {
-        if (system_name[i] == ':') continue;
-        buf.push_back((char)tolower(system_name[i]));
-    }
-    buf.push_back('\0');
-    return std::string(buf.data());
-}
-
-std::string generate_ini_filename(const std::string &ini_dir, const std::string &system_name)
-{
-    std::ostringstream ss;
-    ss << ini_dir << "/" << system_name << ".ini";
-    return ss.str();
-}
-
-std::string generate_par_filename(const std::string &ini_dir, const std::string &system_name)
-{
-    std::ostringstream ss;
-    ss << ini_dir << "/tpchn_" << system_name << ".par";
-    return ss.str();
 }
 
-void output_ini_files(const std::string& ini_dir, const std::string& system_name, std::vector<GeneratorPtr> channels, std::vector<GeneratorPtr> tp_channels, int dcuid)
-{
-    using namespace std;
-
-    string clean_name = cleaned_system_name(system_name);
-    string fname_ini = generate_ini_filename(ini_dir, clean_name);
-    string fname_par = generate_par_filename(ini_dir, clean_name);
-    ofstream os_ini(fname_ini.c_str());
-    ofstream os_par(fname_par.c_str());
-    os_ini << "[default]\ngain=1.0\nacquire=3\ndcuid=" << dcuid << "\nifoid=0\n";
-    os_ini << "datatype=2\ndatarate=2048\noffset=0\nslope=1.0\nunits=undef\n\n";
-
-    vector<GeneratorPtr>::iterator cur = channels.begin();
-    for(; cur != channels.end(); ++cur)
-    {
-        Generator* gen = (cur->get());
-        (*cur)->output_ini_entry(os_ini);
-    }
-
-    for (cur = tp_channels.begin(); cur != tp_channels.end(); ++cur)
-    {
-        Generator* gen = (cur ->get());
-        (*cur)->output_par_entry(os_par);
-    }
-}
-
-unsigned int calculate_ini_crc(const std::string& ini_dir, const std::string& system_name)
-{
-    std::string fname_ini = generate_ini_filename(ini_dir, cleaned_system_name(system_name));
-    std::ifstream is(fname_ini.c_str(), std::ios::binary);
-
-    std::vector<char> buffer(64*1024);
-
-    size_t file_size = 0;
-    unsigned int file_crc = 0;
-    while (is.read(&buffer[0], buffer.size()))
-    {
-        file_crc = crc_ptr(&buffer[0], buffer.size(), file_crc);
-        file_size += buffer.size();
-    }
-    if (is.gcount() > 0)
-    {
-        file_crc = crc_ptr(&buffer[0], is.gcount(), file_crc);
-        file_size += is.gcount();
-    }
-    file_crc = crc_len(file_size, file_crc);
-    return file_crc;
-
-}
-
-
 void usage()
 {
     using namespace std;
-- 
GitLab