2.2. The high performance production readout framework

The high performance production readout framework is a flexible framework for building data sources. Data sources built on top of this framework have the following capabilities:

Many of these capabilities may be meaningless to you now, but as you are involved in increasingly complex systems, you may find uses for them later.

2.2.1. Getting started with the framework

The high performance readout framework consists of two components. The largest component, which implements experiment independent code consists of a set of linkable libraries and headers that are installed in the NSCL DAQ installation directories. The experiment dependent code is distributed as a skeleton of C++ files and headers and a Makefile that build a Readout executable from this skeleton and the libraries.

The NSCL must support not only new experiment development, but many existing, stable experimental setups. To do this we have chosen to install several versions of the NSCL DAQ software on each system simultaneously. Older software gets less support attention, newer software serves as the basis of additional developments.

The NSCL Data acqusition system software is installed at the NSCL in subdirectories under /usr/opt/daq. Each version of the software lives in its own directory tree below this top level directory. For example, nscldaq V7.4 lives in /usr/opt/daq/7.4. In addition, a symbolic link /usr/opt/daq/current points to the directory that contains what is considered the most recent, stable version of the system. /usr/opt/daq/current can point to different directories over time as newer versions of the software become stable. Therefore Makefiles written by the DAQ system installer and by you should refer to the specific version of the DAQ software you are compiling and linking against to ensure that your builds will continue to work as the NSCLDAQ software evolves.

Complicating matters is the version of gnu compilers that are used to build both your software and the NSCL DAQ software. The code generated by the gnu gcc/g++ 3.0 and later compilers is not compatible with code generated by earlier compilers. Additionally, the 3.0 and later compilers are much stricter linguistically than the earlier compilers, requireing source code changes for users porting between these two compilers.

At the NSCL, version 8.0 is the bridge for porting your code between to the gcc 3.x compilers. Therefore, two versions of nscldaq V8.0 are installed at the nscl: /usr/opt/daq/8.0 is an installation compiled against the gcc/g++ 2.95 compilers and /usr/opt/daq/8.0gcc3.3 is a version compiled against the gcc/g++3.3 compilers.

Notwithstanding this discussion of versions, for new application development, you should get all of your skeleton files from the /usr/opt/daq/current directory tree. If you are developing software at an institution that does not follow this installation system, you should contact your local site support staff to determine which directory is the the appropriate top-level nscldaq installation directory. In order to make the rest of this document installation independent, we will use $DAQROOT to refer to the top level installation directory of the DAQ software (/usr/opt/daq/current at the NSCL).

The skeleton files for the high performance production readout framework are located in $DAQROOT/HPpReadoutSkeleton. To start working with this skeleton, you should copy the files from this directory to an empty directory in your account. For example:


cd ~
mkdir readout
cd    readout
cp $DAQROOT/HPpReadoutSkeleton/* .
            

The following files make up the readout skeleton:

Table 2-1. High Performance Production Readout Skeleton file manifest

FilenamePurpose
Skeleton.cppThis contains the implementation of the class that encapsulates user modifications to the skeleton code. Member functions of this class will require modification.
MakefileThis file is a description file for the unix make program. In general you will have to modify this file to incorporate your software into the build of Readout
CTradtionalEventSegment.cppPrior to the use of the production family of event source frameworks, a more primitive Readout Classic framework was used. CTraditionalEventSegment.cpp implements an event segment that allows you to incorporate the readout software from the high performance readout classic framework as an event segment in the high performance production readout framework.
CTraditionalScalerReadout.cppAllows the incorporation of scaler readouts from a high performance classic readout as a scaler bank in the high performance production readout framework.

2.2.2. The process of building a data source from a skeleton

Data sources are built by adding experiment specific code to the readout framework and modifying skeleton files to make the readout framework aware of this new code. The new code implementation files are then added to the Makefile and make is used to build a specific implementation of the framework as an executable named Readout.

The bulk of your programming effort to build a data source will go into creating one or more event segments. Event segments are the readout frameworks's way to modularize the readout.

There is no hard fast rule about what should constitute an event segment, however as you think about how to divide a real experiment readout problem into event segments, you should seriously consider the following suggestions. Following them will make your life easier, and will make the data format sensible.

2.2.3. Event segments

An event segment is an object that is responsible for managing an independent part of the hardware of an experiment. An event segment is responsible for:

An event segment is an instance of a class that inherits from the framework class CEventSegment. The header for this class should be included in all event segment headers via:


#include <CEventSegment.h>
            
The framework Makefile will ensure that $DAQROOT/HPpReadoutIncludes is searched for header files so that the proper version of this header can be included by the preprocessor.

The key part of this header is shown below:

Example 2-1. The CEventSegment Abstract Base Class


class CEventSegment      
{ 

public:
  virtual   void Initialize ()   = 0;     // (1)
  virtual   unsigned short*  Read (unsigned short* rBuffer)   = 0; // (2)
  virtual   void Clear ()   = 0;          // (3)
  virtual   unsigned int MaxSize ()   = 0; // (4)
  
};

#endif

                

The CEventSegment class has a set of pure virtual methods. Pure virtual methods are those that are declared as


type function(args...)  = 0;
            
Pure virtual methods describe an interface which must be implemented by classes that inherit from CEventSegment. Pure virtual methods are a mechanism to ensure that a class that inherits from the base class that defines them must attempt to live up to specific responsibilities that the base class does not know how to implement. CEventSegment and any other class that has pure virtual methods are known as Abstract Base Classes. Abstract base classes cannot generate objects. Only classes that inherit from theM and fulfill the responsibilities defined by all pure virtual methods can generate objects.

Let's look at the responsibilities for event segments as defined by CEventSegment's pure virtual methods.

(1)
Initialize is called by the framework prior to enabling data taking. This function should initialize all hardware managed by the event segment, and prepare it for data taking.
(2)
Read is called in response to a trigger. This function should read the part of the event generated by the event segment. rBuffer is a pointer to some buffer space into which the event segment can be read. The storage available to the event is at least the size of a buffer body. Read should return a pointer to the next free chunk of the buffer. For example, an event segment that just puts 10 words in the buffer that count from 0 to 9 might look like the code below.

unsigned short*
CMyEventSegment::Read(unsigned short* rBuffer)
{
   for (int i=; i < 10; i++) {
      *rBuffer++ = i;
   }
   return rBuffer;
}
                        
(3)
Some hardware may require some action to be performed to clear any latched data prior to responding to subsequent triggers. This member should implement those actions.
(4)
The MaxSize function is obsolete. It is still present for compatibility purposes. It should be implemented as shown below:

unsigned int
CMyEventSegment::MaxSize()
{
    return 0;      // Or any other value you want.
}
                        

Once you have written an event segment you need to arrange for an object of its type to be created and called. To do this you must construct your event segment and register it with the framework. Event segment objects are called in the order in which they are registered.

The Skeleton.cpp skeleton file contains functions that are expected to set up the event readout. Suppose you have created an event segment class named CMyEventSegment, you would register this event segment by modifying the function CMyExperiment::SetupReadout to read as shown below:


void
CMyExperiment::SetupReadout(CExperiment& rExperiment)
{
   CReadoutMain::SetupReadout(rExperiment);

   // Insert your code below this comment.

   rExperiment.AddEventSegment(new CMyEventSegment);

}

            

The call to rExperiment.AddEventSegment adds a new object to the end of the list of event segments managed by the framework. If you had additional event segments to register you would add as many calls to rExperiment.AddEventSegment as needed to register them all.