Using artdaq::Fragment in an experiment-specific manner: a study of demo::V172xFragment / demo::V172xFragmentWriter / demo::V172xSimulator

n.b. To locate the relevant source code files, take a look in the artdaq-demo/artdaq-demo/Generators and artdaq-demo/artdaq-demo/Overlays directories

demo::V172xFragment and demo::V172xFragmentWriter

There are some aspects of an experiment's data fragment which might be of interest that aren't covered by artdaq::Fragment. For example, it's useful for an experiment to represent its fundamental unit of data as an ADC count, which depending on the hardware used isn't guaranteed to be the size of the RawDataType defined for the artdaq::Fragment class-- so for, example, one 64-bit RawDataType might be able to represent two 32-bit ADC counts. Also, it may be that there's information of interest concerning the fragment that's not covered by the artdaq::Fragment header and even the metadata structure -- for example, subdetector-specific information. In artdaq-demo, the artdaq::Fragment overlay class demo::V172xFragment demonstrates how the data in a fragment can be organized in a real experiment; this is used by the artdaq::CommandableFragmentGenerator-derived class demo::V172xSimulator in simulation. The demo::V172xFragment class contains within it a metadata struct :

    struct metadata {
      typedef uint32_t data_t;

      uint32_t serial_number;
      char roc_firmware[6], amc_firmware[6];
      uint8_t v17xx_model, sample_bits;
      uint16_t sample_rate_MHz;
      uint32_t record_length;
      uint16_t post_trigger;
      uint16_t unused[3];

      static size_t const size_words = 8ul;

As well as a header struct :

   struct Header {
      typedef uint32_t data_t;

      typedef uint32_t event_size_t;
      typedef uint8_t channel_mask_t;
      typedef uint16_t pattern_t;
      typedef uint8_t  board_id_t;
      typedef uint32_t event_counter_t;
      typedef uint32_t trigger_time_tag_t;

      uint32_t event_size : 28;
      uint32_t unused_1   :  4;

      uint32_t channel_mask :  8;
      uint32_t pattern      : 16;
      uint32_t unused_2     :  3;
      uint32_t board_id     :  5;

      uint32_t event_counter : 24;
      uint32_t reserved      :  8;

      uint32_t trigger_time_tag : 32;

      static size_t const size_words = 4ul;

As the metadata structure isn't actually used in the V172xSimulator, it will not be discussed further, but simply shown as an example of how an experiment might define its metadata (furthermore, it may be a useful exercise to add to the demo::V172xSimulator::getNext_() function by filling the fragments it generates with metadata). On the other hand, the Header struct is indeed used in the fragments generated in the simulation. It provides a bitfield describing the storage of the following variables:

  • event_size : the event's size _expressed in units of the Header's data_t typedef, not the 64-bit unsigned int RawDataType from artdaq
  • channel_mask : an unsigned 8 bit integer which allows for the fragment's data to be subdivided into 8 channels numbered 0-7; a channel mask of 1 would mean only channel 0 was enabled, a channel mask of 3 would mean channels 0 and 1 were enabled, etc. Not used in the code employed in artdaq-demo.
  • pattern : unused; desired usage unclear
  • board_id : an identifier for the upstream DAQ board which sent the fragment
  • event_counter : set to N, where this fragment comes from the Nth event collected
  • trigger_time_tag : used to save what point in time the event was triggered on

Please note that, as in the case of artdaq::Fragment's header, these variables should not be altered directly but rather controlled through setter-functions; however, the necessary setter functions are not defined in demo::V172xFragment itself, as this class only contains const getter-functions. Rather, the setter functions are found in demo::V172xFragment's derived class, demo::V172xFragmentWriter. While a fuller discussion of the demo::V172xSimulator class will come later, if you look at the source file which implements demo::V172xSimulator and skip to line 139, what you see is the following code inside a loop designed to fill a vector of pointers to artdaq::Fragment objects called "frags":

   frags.emplace_back(new artdaq::Fragment);
    V172xFragmentWriter newboard(*frags.back());
    newboard.setBoardID( board_id() );

First, a default-constructed artdaq::Fragment is created and its pointer pushed onto the frags vector. This object is then passed to the V172xFragmentWriter constructor which then calls a function called V172xFragmentWriter::resize(). The call to resize() will take as an argument a number of ADC values to be held by the fragment (nChannels_) and in the body of the function it will then resize both the vals_ vector in the artdaq::Fragment, where the data is physically stored, as well as the event_size variable in the V172xFragment's header.

Please note that the size of an ADC count is typedef'd in the demo::V172xFragment declaration as a 16-bit unsigned integer, as opposed to a 32-bit unsigned integer being the size of the "data_t" datatype typedef'd in demo::V172xFragment::Header and also as opposed to a 64-bit unsigned integer being the size of the RawDataType typedef'd in the artdaq::Fragment class; this means that a call to frags.back().dataSize() (which gives us the number of RawDataTypes beyond the header and metadata of the artdaq::Fragment we're working on) would not return the same value as was passed as an argument to resize() nor the same value as the demo::V172xFragment::Header's event_size variable, accessible via newboard.event_size() . Then, the V172xFragmentWriter sets the V172xFragment header's board_id and event_counter. Please note that in both the demo::V172xFragment class and its derived demo::V172xFragmentWriter class the only data is a reference to an artdaq::Fragment object, whose data in turn consists solely of the "vals_" vector. In other words, all writes ultimately involve manipulating the bytes which make up the vals_ vector.


This brings us to the demo::V172xSimulator class. Derived from artdaq::CommandableFragmentGenerator, demo::V172xSimulator adds experiment-specific functionality not covered by artdaq::CommandableFragmentGenerator . Looking at the implementation of the constructor on line 86 of the file, we see that its argument is a FHiCL parameter set, used to control the class's generation of fragments on calls to its getNext_() function. Among these parameters are the variables "nChannels", "fragment_type", as well as "freqs_path" (defaulting to "DAQ_INDATA_PATH") and "freqs_file". "nChannels" represents the number of ADC counts per fragment we expect and should not be confused with the (currently unused) "channel_mask" variable that's part of the demo::V172xFragment::Header; "fragment_type" can be set to "V1720" or "V1724" and will define whether the first 12 or 14 bits of the value type representing an ADC count will be considered meaningful. "freqs_path" is the name of an environmental variable which in turn lists colon-separated directories, and "freqs_file" is the name of an ASCII file with a "*.dat" extension which describes the relative frequencies of all possible ADC values produced in simulation; see "./artdaq-demo/test/Generators/V1720_sample_freqs.dat" and "./artdaq-demo/test/Generators/V1720_sample_freqs_uniform.dat" for examples of this. Note that in both *.dat files there are five columns; the only column used in the simulation is the first column. Also note that after sourcing setupARTDAQDEMO as described in Running a sample artdaq-demo system the variable DAQ_INDATA_PATH gets set. The *.dat file is read in and used to fill a std::discrete_distribution object which will then generate ADC values randomly according to the desired frequencies; thus if "freqs_file" is set to "V1720_sample_freqs.dat" values will be sharply peaked in the range 3930-3950 (out of a possible 0-4095), and as you'd expect from the name "V1720_sample_freqs_uniform.dat" will cause ADC values to be produced with equal probability over the entire 0-4095 range.

The actual generation of fragments occurs in demo::V172xSimulator class's getNext_() function, on line 127. Some of its code has been alluded to above; it will create artdaq::Fragment objects and set variables both via the demo::V172xFragmentWriter class, as well as via direct calls to artdaq::Fragment::setFragmentID(), artdaq:;Fragment::setSequenceID() and artdaq::Fragment::setUserType(). In this way you can see how the information in the fragment can be set using artdaq functionality as well as experiment-specific functionality.