Unpacking data in SpecTcl

This section will present:

Note that throughout, this page and examples, we will make use of the data and functions that are defined in the CCAENV1x90Data namespace. These definitions can be incorporated in your code as follows:

In your implementation software add the header:

#include <CCAENV1x90Data.h>

This header file is located in the DAQ library include file directory (e.g. /usr/opt/daq/{version}/include) where {version} is the version of the daq client software you are using (The first version that supports this module is 7.4 e.g. /usr/opt/daq/7.4/include).

You will need to modify your SpecTcl Makefile to search this directory for headers. To do this locate the definition of USERCXXFLAGS in your Makefile (SpecTcl 2.2 and later), and add the appropriate -I switch for example:

USERCXXFLAGS=-I/usr/opt/daq/7.4/include

Get help from the computer group if you are using versions of SpecTcl that pre-date 2.2 if needed.

Structure of the data produced by the TDC module

Refer to figures 6.1 through 6.7 of the hardware manual when reading this section. These figures depict the data that the module produces.

The data from the TDC consists of a series of long words. Bits 27-31 of each longwords describe the longword type. The longword types produced by the module include:

Strategies for unpacking the data.

By far the worst issue an unpacker for this module will have to face is the multihit nature of the module. Assuming that you need to histogram all of the raw hits from each channel, the simplest way to deal with this is to define parameter sets for each channl, one parameter for the first hit, another for the second and so on. A 'summed' spectrum over all the hits can be created using a gamma spectrum. Individual hit spectra can also be produced to support gating on e.g. the second hit in channel 5. An or gate of all of these for a channel produces a gate for the multiple hit containing at least one hit inside the slice. If you are using TreeParam, you can create hit arrays to effectively manage these parameters.

In our sample code we will create hit parameters limited to 16 hits per channel.

A sample event processor that unpacks TDC data.

This section assumes that:

In order to focus attention on the process of unpacking the data:

We will create a custom event processor to handle this module. The event processor will be a class that is defined in the header v1290processor.h, implemented in v1290processor.cpp, and hooked into SpecTcl via code added to MySpecTclApp.cpp. The class will be called C1290Processor.

To hook this unpacker into SpecTcl, we must include the v1290processor.h header This is shown below:

#include "MySpecTclApp.h"                               
#include "EventProcessor.h"
#include "TCLAnalyzer.h"
#include <Event.h>
#include "v1290processor.h"     // <---- Added.

We will also need to create an instance of a C1290Processor object and add it to the list of event processors that are executed by SpecTcl for each event. We will assume that the constructor of the event processor will require two parameters:

This will allow the unpacker to be used in other environments.

To do this we need to make the following changes to CMySpecTclApp::CreateAnalysisPipeline

void 
CMySpecTclApp::CreateAnalysisPipeline(CAnalyzer& rAnalyzer)  
{ 

#ifdef WITHF77UNPACKER
  RegisterEventProcessor(legacyunpacker);
#endif
  
// Removed:    RegisterEventProcessor(Stage1);
// Removed:   RegisterEventProcessor(Stage2);

   RegisterEventProcessor(*(new C1290Processor(2, 0)));  // <-- Added.
}  

The event processor class header must:

In our case, we will need to keep the Virtual slot number and the base parameter id. To do this we will need to implement a constructor. We will also need to implement the function call operator (operator()) to unpack the data for each event. This leads to the following as the contents of v1290processor.h

#ifndef __V1290PROCESSOR_H
#define __V1290PROCESSOR_H         // (1)

#include <EventProcessor.h>

class C1290Processor : public CEventProcessor // (2)
{
private:
  unsigned int m_nVsn;                        // (3)
  unsigned int m_nBaseParameter;              // (4)
public:

  C1290Processor(unsigned int nSLot, 
                 unsigned int nBase);         // (5)

  virtual Bool_t operator()(const Address_t pEvent,
                            CEvent& rEvent,
                            CAnalyzer& rAnalyzer,
                            CBufferDecoder& rDecoder); // (6)
  
};
#endif

  1. This ifdef/define pair ensures that if the header is included twice it is non functional the second time. In complex software this is a must. In simple software this is still recommended.
  2. This starts the class definition. The class has the CEventProcessor as its base class.
  3. m_nVsn will be the virtual slot number of the module.
  4. m_nBaseParameter will be the number of the first parameter used by this unpacker/event processor.
  5. This constructor will be defined to intialize m_nVsn and m_nBaseParameter from its actual arguments.
  6. This function will be called once for each event. pEvent will point to the raw event data, while rEvent is the 'array' of parameters that need to be filled in from the unpacked data. The other parameters are not needed by this and most event processors.

Let's now take up the implementation of the unpacking class (v1290processor.cpp). The constructor implementation is shown below:

C1290Processor::C1290Processor(unsigned int nSlot,
                               unsigned int nBase) :   // (1)
  m_nVsn(nSlot),                                       // (2)
  m_nBaseParameter(nBase)                              // (3)
{
  
}

  1. This begins the implementation of the C1290Processor class constructor. This function will be called whenever a C1290Processor object is created. The colon at the end of what looks like a nearly normal function header indicates that this constructor, in turn, will call other constructors. The constructor ivocations (2,3) follow the colon as a comma spearated list.
  2. This is a constructor invocation for the m_nVsn member data of the class. m_nVsn is initialized to the value of the nSlot parameter.
  3. This is a constructor invocation for the m_vBaseParameter member data. m_nBaseParameter is initialized to the value of the nBase parameter.

The unpacking operator is a bit more interesting. There are several things we need to do:

Bool_t
C1290Processor::operator()(void*           pEvent,
                           CEvent&         rEvent,
                           CAnalyzer&      rAnalyzer,
                           CBufferDecoder& rDecoder)
{

  // Establish a word translating buffer pointer.

  TranslatorPointer<UShort_t> pw(*(rDecoder.getBufferTranslator()), 
                                 pEvent);                // (1)

  UShort_t  nWords = *pw++;                              // (2)



  // Report the event size:                                 (3)

  CTclAnalyzer& rAna(static_cast<CTclAnalyzer&>(rAnalyzer));   
  rAna.SetEventSize(nWords*sizeof(UShort_t));          

  ULong_t    nLongs = (nWords-1)*sizeof(UShort_t)/sizeof(ULong_t); (4)

  int nSlot     = -1;                                    
  bool FormatOk;
  long hits[128][MAXHITSPERCHANNEL];                    // (5)
  long nHits[128];
  for(int i=0; i < 128; i++) {
    nHits[i] = 0;
  }

  while (nLongs) {

    ULong_t data = *pw;
    ++pw;
    data        |= *pw << 16;                          // (6)
    ++pw;

    nLongs--;
    if(CCAENV1x90Data::isGlobalHeader(data)) {               
      nSlot = CCAENV1x90Data::BoardNumber(data);       // (7)
    }
    if(CCAENV1x90Data::isTDCHeader(data)) {                  
    }
    if(CCAENV1x90Data::isTDCTrailer(data)) {                 
    }
    if(CCAENV1x90Data::isTDCError(data)) {                   
    }
    if(CCAENV1x90Data::isMeasurement(data)) {                
      int nChannel = CCAENV1x90Data::ChannelNumber(data, false);
      long nValue  = CCAENV1x90Data::ChannelValue(data, false);
      if(nSlot == m_nVsn) {
        if(nHits[nChannel] < MAXHITSPERCHANNEL) {
          hits[nChannel][nHits[nChannel]] = nValue;   // (8)
          nHits[nChannel]++;
        }
      }
    }
    if(CCAENV1x90Data::isGlobalTrailer(data)) {              
      if(nLongs) {
        FormatOk = false;
      } else {                                       // (9)
        FormatOk = (nSlot == m_nVsn);
      }
    }
  }
  // Store at most MAXHITSPERCHANNEL hits from each channel from the hits
  // vector.

  if(FormatOk) {                                    // (10)

    // Channel 0 is the gate.  There must be exactly one gate hit to
    // get decent resolution, all channels must be subtracted from the
    // digitized time of the gate as the gate time used in trigger subtraction
    // is only good to 25ns:

    if(nHits[0] == 1) {
      
      long nGateTime = hits[0][0]; // Gate time.    // (11)
      
      int nParam = m_nBaseParameter;
      for(int nChan = 1; nChan < 128; nChan++) { // Real chans start at 1.
        for(int ihit = 0; ihit < nHits[nChan]; ihit++) {
          rEvent[nParam + ihit] = nGateTime - hits[nChan][ihit]; // (12)
        }
        nParam += MAXHITSPERCHANNEL;
        
      }
    }

  }
  return kfTRUE;                                                // (13)
}

  1. This line establishes a translating buffer pointer for the event. Translating buffer pointers automatically handle differences in byte ordering between systems. Using translating buffer pointers allows data taken at the NSCL on Intel Linux systems to be analyzed transparently on e.g. MAC OS-X or Sun Solaris systems, where the internal bytes of a word are ordered differently.
  2. The first word of the event is the size of the event in words. We keep a copy of this to be able to determine when we have reached the end of the event.
  3. SpecTcl needs to know how large each event is in bytes. It uses this event size to determine where the next event in a multi-event buffer starts. The event size is given to SpecTcl by calling the analyzer's SetEventSize member function. This pair of lines casts the analyzer to its actual type (a CTclAnalyzer) and invokes that member function.
  4. The TDC module produces longword data. This line calculates the number of longwords of data in the event (exclusing the word count).
  5. Several pieces of information will be produced by the first phase of unpacking (the data decode). These are the module's virtual slot number (nSlot). A determination that the event is properly formatted (FormatOk). The hits (hits) will be stored in an array of MAXHITSPERCHANNEL for each of the 128 channels in the module. For each channel, we also save the number of hits (nHits). This array is zeroed since initially we have no hits.
  6. This line reconstructs a longword of data from the buffer.
  7. If the data longword from the TDC is a global header, the board number (virtual slot) is decoded and saved in nSlot.
  8. If the data longword is a measurement (Tdc value). The channel and value are decoded. If, for that channel, MAXHITSPERCHANNEL hits have not yet been decoded, the next hit position for that channel is used to store the hit time and the number of hits for that channel is incremented.
  9. If the longword is a global trailer, two sanity checks are performed. If the number of longs is not yet zero, the event is badly formatted (this will not be the case if the tdc data is mixed with data from other devices). If the module's virtual slot does not match the slot we are decodeing, this indicates we have unpacked data from the wrong module and the unpacked data should not be converted to parameters.
  10. The decoded hits are stored in parameters only if FormatOk is true.
  11. We also require there be exactly one hit in channel 0, the digitization of the trigger time. This is saved in nGateTime.
  12. Each hit is then put in an associated parameter after its time relative to the trigger time is computed.
  13. The unpacking function returns true indicating that other event processors in the pipeline can be called, and the event histogrammed.

Generated on Wed Sep 17 08:38:10 2008 for NSCL Device support. by  doxygen 1.5.1