Pulse Programmer Overview

The pulse programmer (PP) generates all the electrical timing signals that control the NMR experiment. This allows all aspects of experiment timing to be synchronized to a single time base, so that the timing relationship between events will always be fully predictable and under the control of the experimenter.

The experiment is described as a sequence of states. A state describes the condition of all electrical outputs for a specified period of time. The description of each state contains many fields.  For example, the timing field specifies the duration of the state and each group of outputs controlled by the PP has a corresponding output field.  Furthermore, there are special control fields that specify special actions to be taken during or at the end of the state.

The pulse programmer is based on a FIFO ( "First In First Out") state memory which is 65536 words in length.  Items written into the FIFO are stored until such time as they are read out. Writing an item increases the number of items stored in the FIFO by one.  Likewise, reading an item decrements the number of items stored in the FIFO.  Items always read out in the same order as they are written in, this is what is meant by "First In First Out". The FIFO memory is 65536 words in length and each word in the FIFO memory holds one complete state.

The pulse program is an executable that runs on the host computer (the SGI workstation) and generates all the states that will occur in the experiment, in the order which they will occur, and writes them into the FIFO. If the FIFO becomes full, the program simply waits until space is available before continuing. The pulse programmer removes state words from the FIFO sequentially and expresses each state for the duration specified by that states timing field.

The pulse programmer also has several features that extend this basic model of operation. These include strobe outputs, repeatable states, and callable subprograms. See Advanced Pulse Programs and NMR Console Design Report 4.2.2 and 4.2.3 for a detailed explanation.


Pulse Program Basics

A pulse program is a specially written "C" program that utilizes a library of macros and functions to specify the exact sequence of events in an experiment.  Such a program performs all the tasks associated with running an experiment and collecting the resulting data.  Although each different kind of experiment requires a unique pulse program, all such programs share common structural elements.

The pulse program consists of a user written "C" language function, always named "pulse_progam()", which is linked to a library of support routines. A variety of "C" preprocessor macros are provided to greatly simplify writing the pulse program. The purpose of the pulse program is to generate a sequential list of states that describe an experiment. When the pulse program is executed it loads the list of states into the memory of the pulse programmer and commences execution of the experiment

In a pulse program, the actual work of generating the states that describe the experiment is done by calls to the function state().  Each call to this function generates one complete state to be loaded into the pulse programmer.  A pulse program makes as many call to state() as is necessary to generate all the states in the experiment.  The pulse program makes the calls to state() in the same sequence that the states will be expressed at the outputs of the PP.

The state() function accepts a variable length argument list; as many arguments may be included as are required to fully define the state.  The arguments are actually specified by using predefined macros.  Each possible field in a PP state has an associated macro that generates the arguments for state() that are required to specify that field.  These macros are referred to as primitive macros or just primitives.  For example, the primitive macro Time(secs) generates arguments that will cause the timing field to be loaded with a value corresponding to the period of time "secs".  Various higher level macros implement one or more calls to state() to perform common functions. The are referred to as program macros or just macros.

An important aspect of pulse programs is their use of the symbol table. Since it is frequently necessary to change controlling parameters in a pulse program while setting up an experiment (pulse lengths for example) it would be very inconvenient to re-compile and re-link the pulse program each such time a change is made.  Instead, the pulse program reads parameter values from an external symbol table each time it is executed.  The values in the external symbol table can be changed by the user by using either the define command or the symbol editor graphical user interface.

The user written pulse program is actually in the form of a function named pulse_program().  When the user's pulse program is compiled and linked, it is linked to a main() function that is supplied from the system library.  This allows the system to take of housekeeping tasks both before and after the user written code is executed.


Simple Example Pulse Program

This is a simple example of a functional pulse program.  Each section of the program contains a comment with a hyperlink to a an explanation.

/*
 * npulsepc.c
 *
 * March 1996
 * James Gladden
 *
 * A simple pulse program to perform a one pulse phase cycling experiment.
 * Data is accumulated until the specified number of scans is completed.
 *
 */

/* Include the pulse program header file */

#include "pulse.h"

/* Declare the global variables that will be used to hold values extracted from the symbol table */

double scans;                           /* Number of scans per experiment */
double dwell;                           /* Dwell time*/
double size;                            /* Number of points in FID */
double relax;                           /*Relaxation delay */
double rec_phase;                       /* Receiver phase */
double xmit_phase;                      /* Xmit phase */
double xmit_amp;                        /* Xmit amplitude */
double t90;                             /* xmit pulse width */
double ad_bits;                         /* A-D converter type */
double sf1;                             /* Resonant frequency */
double o1;                              /* Offset frequency */
double filter;                          /* Filter Frequency */
double gain;                            /* Receiver gain */
char * fname;                           /* Output file name */

/* Start of the pulse_program()function */

void pulse_program()
{

/* Declare the local variables. */

        int j, phase_reversal_flag;
        double lo_freq, phase_cycle;

/* Get/Set symbol table values. */

        scans           = symbol("scans",       16,     1,      1e6);
        dwell           = symbol("dwell",       5e-6,   100e-9, 1e6);
        relax           = symbol("relax",       2,      100e-9, 1e3);
        size            = symbol("size",        1024,   8,      1e6);
        rec_phase       = symbol("rec_phase",   0,      0,      360);
        xmit_phase      = symbol("xmit_phase",  0,      0,      10000);
        xmit_amp        = symbol("xmit_amp",    1,      -1,     1);
        t90             = symbol("t90",         10e-6,  200e-9, 100);
        ad_bits         = symbol("ad_bits",     16,     12,     16);
        sf1             = symbol("sf1",         500e6,  10e6,   1100e6);
        o1              = symbol("o1",          0,      -10e6,  10e6);
        filter          = symbol("filter",     4e3,     400,    1.5e6);
        gain            = symbol("gain",        50,     0,      90);
        fname           = tsymbol("outfile",    "temp.dat");

/* Set global symbol structure values. */

        symbols.sw    = 1.0/dwell;
        symbols.size  = size;
        symbols.scans = scans;

/* Assign a display name to the ESR used to keep track of the scan count.*/

        assign_name(CONTROLLER_1, ESR_1, "Scans");

/* Initialize the data system */

        initialize_data_system(fname);

/* Calculate the local oscillator frequency */
	lo_freq1 = calculate_lo_freq( sf1 + o1, FREQ1, &phase_reversal_flag );

/* Define the default state. */
/* The default state defines the condition of all
   outputs that are not explicitly set in a given state. */

        default_state( Freq1(lo_freq), Rec_filter(filter),
                       Rec_gain(gain), Time(200e-9) );

/* Start of the pulse program state generation. */
/* Insert an initial delay to allow "slow" responding controls, such as
   receiver gain to settle to the value specified by the
   default state. */
        state( Time(10e-3) );

/* Send messages to the DAP specifying which AD converters to use and
   the number of points in the FID. */
        AD_TYPE(ad_bits);
        FID_SIZE(size);
	PHASE_ROTATION_DIRECTION(phase_reversal_flag);
	

/* Clear the FID buffer */
        CLEAR_FID_BUFFER;

/* Enter the scan loop */
        for (j=0; j < scans; j++) {

/*       Calculate the phase shift for this scan */
            phase_cycle = (j % 4) * 90.0;

/*       Load the Experiment State Register with the scan number */
            LOAD_ESR(1, j+1);

/*       Pulse the transmitter */
            PULSE1(xmit_amp, xmit_phase + phase_cycle, 0xf, t90);
/*       Collect the FID */
            FID(rec_phase + phase_cycle, dwell, 100e-6, size);

/*       Start the relaxation delay, update display, and
         transfer the data */
            RELAX(relax, j, scans);

        }

printf("\nProgram Done\n");
        return;
}

At a minimum, the 'stubs' for pulse_program_2/3/4 must exist in this file.  

void pulse_program_2()
{
        return;
}

void pulse_program_3()
{
        return;
}

void pulse_program_4()
{
        return;
}

Example Pulse Program Details

This section contains explanations of each part of the simple example pulse program in the preceding section.  Each of the explanations in this section can be reached from the following table of contents, or from the links embedded in the example pulse program.

  •  Required Header File
  • Global Variables
  • The pulse_program() Function
  • Local Variables
  • Reading and/or Writing the Symbol Table
  • Assigning Values to the Global Symbol Structure
  • Assign Names to ESRs
  • Initialize the Data System
  • Calculate the Local Oscillator Frequency
  • State Generation
  • Initial Delay
  • DAP Messages
  • The Scan Loop
  • Load the ESR
  • Pulse the Transmitter
  • Collect the FID
  • Start the Relaxation Delay
  • The 'Stubs' for Additional Pulse Programs
  • Required Header File

    All pulse programs must include the header file "pulse.h" by using a standard preprocessor include statement of the form:

    #include "pulse.h"

    This file contains the definitions for the primitive macros and program macros that are used in constructing pulse programs. It also includes function prototypes for support functions that are called by pulse programs as well as declarations of global variables and structures. Additionally, this file includes the standard system header files "stdio.h" and "math.h" as a convenience.

    Global Variables

    By convention, all program variables that are used to hold values read from the external symbol table are declared as global variables.  Thus if the pulse program is elaborate enough to contain additional functions these variables will be accessible to all functions in the file.  Also by convention, these variables are of type "double" with the exception of those used for text strings (such as the output file name) which are of type "char *".

    In the "C" language all variables declared outside the body of a function are global.

    The pulse_program() Function

    The entry point of a user written pulse program is always a function named pulse_program().  When the user's pulse program is compiled and linked, it is linked to a main() function that is supplied from the system library.  This allows the system to take care of housekeeping tasks both before and after the user written code is executed.

    Local Variables

    Any variables that are local to the pulse_program() function (that is, they are only use within that function) are usually declared immediately after the function header.  Be aware that in the "C" language such local variables are not automatically initialized to zero, so if the value of such a variable is used before the first assignment to it the results are unpredictable.

    Reading and/or Writing the Symbol Table

    The first action taken by the pulse_program() function is usually to make calls to the function symbol() to extract values from the external symbol table.  These call are of the general form:

    t90 = symbol("t90", 5e-6, 1e-6, 1e-3);

    The character string argument is the name of the symbol as it appears in the external symbol table and the three numeric arguments are the default value for the symbol, the minimum allowable value, and the maximum allowable value, in that order.

    A similar function, tsymbol(), is used for symbols which hold a character string value rather than a numeric value.

    These functions operate in two different ways, depending on how the pulse program is executed.  In the normal case, the function looks up the value of the symbol with the name specified by the character string argument, and returns that value.  If the symbol is not found in the table a fatal error results.  However, if the pulse program is executed with the "-d" command line argument, the argument requests that the program build a default symbol table, rather than actually run the experiment.  In this case, each call to symbol() or tsymbol() creates an entry in the new symbol table; the entry is created using the default, minimum, and maximum values specified as function arguments.

    Assigning Values to the Global Symbol Structure

    It is useful to record the values of certain program parameters in the spectrometer output data file in a manner such that they can easily be extracted by other programs. For examples, when a Felix data file is extracted from a spectrometer output data file, it is important to be able to extract such parameters as the FID size, the spectral width, etc., and record them in the Felix data file header.

    The structure "symbols" is used to record parameters in the output data file. This structure is always written to the output data file. A complete list of the structure elements can be found in Global symbol structure. The pulse program should explicitly assign values to structure elements as appropriate to the needs of the program. Examples of such assignments are:

    symbols.size = size; /* FID size */
    symbols.sw = 1/dwell; /* Spectral width */

    Assign names to ESRs

    Experiment state registers (ESRs) are registers in the pulse programmer. Each controller in the pulse programmer has three ESRs.  Any state in a pulse program can optionally write a value to one of the ESRs.  These registers are commonly used to keep track of such things as the current scan number and/or experiment number.  The current contents of ESRs are displayed by the spectrometer software,  but the value of a given register is only displayed if the pulse program has assigned a name to the register.  The function assign_ESR_name() is used for this purpose as in the following example:

    assign_ESR_name(CONTROLLER_1, ESR_1, "Scans");
    assign_ESR_name(CONTROLLER_1, ESR_2, "Experiments");

    In this case the names "Scans" and "Experiments" are assigned to ESR registers 1 and 2 respectively in controller 1. Thus the names and contents of these two registers will be displayed.

    Initialize the  Data System

    After reading the external symbol table the pulse_program() function must open and initialize the output data file with the following function call:

    initialize_data_system(fname)

    The argument "fname" is a pointer to a character string which contains the data file name.

    If the pulse program is executed with the "-d" command line flag, indicating that it should build a new symbol file rather than actually load states into the pulse programmer, this function will cause a program exit rather than performing its normal function.  It is thus important that this call appear in the pulse_program() function after all calls to symbol() and tsymbol() have been made,  but before any calls to state() are made.

    Calculate the Local Oscillator Frequency

    The function calculate_lo_freq() returns the local oscillator frequency (LO) necessary to produce the desired transmitter output and/or receiver observe frequency.

    lo_freq1 = calculate_lo_freq( sf1 + o1, FREQ1, &phase_reversal_flag );

    The first argument specifies the desired operating frequency.  The second argument is a predefined constant that specifies which of the four possible transmitter channels is being used (FREQ1, FREQ2, FREQ3, or FREQ4).  

    The third argument is passed as a pointer so that the function can return a value to this argument.  This returned value is only relevant if the frequency being calculated will be used as the receiver LO.  If the function selects an LO frequency which is above the requested operating frequency, then the receiver first mixer will subtract the input RF from the LO to generate the IF.  This will result in a reversal of the apparent sign of frequencies at the receiver's detector output, which will ultimately result in a left-to-right swap of peaks in the Fourier transform result.  In this case the returned value will be one.  If no reversal occurs the returned value will be zero.  This value can later be passed to the PHASE_ROTATION_DIRECTION() macro to send a message to the DAP requesting that the frequency inversion be corrected if necessary.  See DAP Messages for an example.

    Define the Default State

    The pulse programmer outputs directly control almost all aspects of the spectrometer. Thus each state in a program must specify the condition of all outputs that are relevant to the experiment being performed.  In most experiments there will be some pulse programmer outputs that do not change doing the course of the experiment.  Typical examples are the synthesizer frequency (or frequencies) and the receiver filter frequency.  If these outputs are constant over the entire experiment it is convenient to be able set them once and not have to specify their value as part of each state that is generated.  This is done with the default_state() function. Here is a typical call:

    default_state( Freq1(f1_lo), Rec_filter(filter), Time(200e-9) );

    This call specifies that,  in all states where no other value is specified, synthesizer 1 shall be set to a frequency of "f1_lo",  the receiver anti-aliasing filter shall be set to a frequency of "filter", and the duration shall be 200 nanoseconds.  Outputs that are not assigned a default value are zero doing all states that do not explicitly assign a value.  Note that if a value is explicitly assigned in a state the assigned value overrides the default value for the duration of the state.

    State Generation

    The principal task of pulse_program() is to generate the list of states that describe the experiment and load them into the pulse programmer FIFO memory.  This is done by calls to the function state(); each such call generates one state and loads it into the pulse programmer FIFO.  The state() function accepts a variable length list of arguments that define the fields in the state.  Fields that are not defined have the value assigned in the default state or, if they were not defined in the default state either, have a value of zero.  The field arguments to the state() are supplied using macros called primitives.  A macro primitive exits for each possible field in a state.

    Here is a an example:

    state( Xmit1_amp(amp), Xmit1_phase(phase), Xmit1_gate(gates), Time(period) );

    This state produces a pulse on transmitter 1 by specifying duration, amplitude, phase, and the condition of the transmitter gates.  Four different primitives are used to specify the fields in the state.

    Pulse program writing can sometimes be simplified by the use of program macros (referred to simply as "macros") which define certain frequently used states or sequences of states. For example, the previous state can be generated by using the PULSE1 macro.

    Repetitive sequences of states are usually generated by placing state() functions call inside a loop such as a "for" loop.  See The Scan Loop for an illustration of this technique. 

    Initial Delay

    The first state in a program is usually quite simple:

    state( Time(10e-3) );

    This state specifies only a duration; 10 milliseconds.  All other fields assume their default values or zero if they were not defined in the default state. The purpose of this state is to allow "slow" spectrometer controls (such as the relays that control receiver gain) to settle to there default values before the actual experiment begins.

    DAP Messages

    After generating the initial delay a pulse program typically generates one or more states whose sole function is to send messages to the Data Acquisition Processor (DAP).  The example program generates four such states, each of which sends a different message to the DAP.  Since these are all commonly used messages, there are predefined macros for generating the states:

    AD_TYPE(ad_bits);

    This macro generates a state which tells the DAP which A-D converters should be used (12 bit or 16 bit);

    FID_SIZE(size);

    This macro generates a state which tells the DAP how many points will be collected in the FID.  The DAP needs this information so that it will know how many data points should ultimately be transferred to the work station.

    PHASE_ROTATION_DIRECTION(phase_reversal_flag);

    This message tells the DAP to reverse the rotating frame of the data if the receiver is being operated in a mode that results in frequency inversion.

    CLEAR_FID_BUFFER;

    This message tells the DAP to clear the data buffer by writing zeros to all locations.

    The Scan Loop

    Most pulse programs contain a loop that generates most of the states in the experiment.  In the case of this example, each iteration of the loop generates the states in one "scan" of the experiment.  More complex programs often have nested loops. 

    This program uses the "C" language "for" statement to control the loop:

    for (j=0; j < scans; j++) {
    }

    This statement starts the loop with the variable "j" equal to zero, and continues to loop as long as "j" is less that the value of "scans".  The variable "j" is incremented at the end of each loop repetition.  The loop executes all the statements that are between the braces {} that delimit the body of the loop.

    Load the ESR

    This pulse program has already assigned the name "Scans" to Experiment State Register 1 (see Assign Names to ESRs).  This statement:

    LOAD_ESR(1, j+1);

    assigns the value of the scan loop counter (variable "j") to ESR 1 at the beginning of each repetition of the loop.  This will automatically update the status displays produced by the Manager and FID Display programs.

    It is also makes the value available to the conditional stop/pause mechanism so that the user can request that the experiment halt or pause at the completion of a particular scan.

    Pulse the Transmitter

    This example uses a macro to generate a state during which transmitter 1 is turned on with a specified phase and amplitude for a specified duration.

    PULSE1(xmit_amp, xmit_phase + phase_cycle, 0xf, t90);

    This macro actually generates a call to state() that looks looks like this:

    state( Xmit1_amp(xmit_amp),
           Xmit1_phase(xmit_phase + phase_cycle),
           Xmit1_gate(1 | ((int) 0xf  << 1 )),
           Time(t90) );

    The argument "0xf" is a hexadecimal constant specifying the state of the gate outputs on the back of the transmitter.  In this case it specifies that all four gate outputs should be turned on for the duration of the state.

    Collect the FID

    This program uses a macro to generate the states that perform the actions necessary to digitize the FID and store results in the DAP memory.  The macro invocation looks like this:

    FID(rec_phase + phase_cycle, dwell, 100e-6, size);

    The arguments specify the receiver phase,  the dwell time between points, the dead time,  and the number of points to be digitized, in that order.

    Start the Relaxation Delay

    Most pulse programs contain a relaxation delay after the FID has been digitized.  This is also the point in the experiment where the PP should send a message to the DAP requesting that the host workstation be notified that new FID data is available for display.  And, if this is the last scan in the experiment, a message should also be sent requesting that the host write the FID data to the data file.  All of these functions are performed by this macro:

    RELAX(relax, j, scans);

    The first argument is the length of the delay, the second is the current scan number, and the third is the intended total number of scans.  The second and third arguments are necessary so that the macro can determine which invocation is the last relaxation delay in an experiment.

    The 'Stubs' for Additional Programs

    The pulse programmer can have up to four controller modules installed, and each module can run a separate pulse program.  The various output fields can be allocated to different controllers so that each pulse program can control a different set of outputs.  For more information about such features see Advanced Pulse Programs.


    Primitives

    Primitives are pre-processor macros that are used to specify arguments for the state() function. Here is an example:

    state( Xmit1_phase(90.0), Xmit1_amp(1.0), Xmit1_gates(15), Time(5e-6) );

    This example generates a state in which transmitter one is turned on with a phase of 90 degrees, an amplitude of 1.0, and four gate control bits asserted.  The state lasts five microseconds.  Each primitive is a macro that generates the arguments to state() necessary to specify the contents of one field in the pulse programmer memory word. There is no restriction on the order in which the primitives appear in the state() argument list.

    By convention the names of primitives start with a capital letter and the remaining letters are all lower case.  Some primitives, such as Control, require pre-defined constants as arguments.  The DAP Software and PP Controller Card documents specify values for these constants and also define symbolic names for them.  These names are also available as macros which can be used instead of the constants.  By convention, the corresponding macro names are all upper case with any spaces or punctuation replaced by "_" characters.  If an argument is bit encoded, and more than one bit needs to be asserted,  the values should be "ored" together and the result used as the argument.

    The current list of primitives is described below:

    Ad_phase(phase)

    Set the phase by which a sampled point is rotated before being summed to the FID buffer (DAP software 1.2.1). The argument is in degrees.
     
    Ad_disposition(cmd)
    Set the Digitizer Command "sample disposition field" (DAP software 1.2.2).
     
    Ad_pointer(cmd)
    Set the Digitizer Command "buffer pointer control field" (DAP software 1.2.3).
     
    Dap_msg(cmd)
    Set the PP Command Register (DAP software 1.3).
     
    Rec_gain(gain)
    Set the receiver gain in dB units. the minimum allowed value is 0 and the maximum is 90. Since receiver gain is controllable in multiples of 6 dB, values which are not such multiples will be rounded off to the nearest multiple.
     
    Rec_filter(filter)
    Set the receiver lowpass filter cut-off frequency in Hertz..
     
    Rec_gate(gates)
    Set the receiver gate. A value of 0 opens the gate switch (blocks the signal), a value of 1 closes it (allows the signal to pass).
     
    Time(period)
    Set PP controller card Timing field (PP Controller Card 3.2.1). This value determines the duration of a state and is specified in seconds. The minimum allowed value is 100e-9 seconds, the maximum is 214.7483648 seconds.
     
    Repeat(value)
    Set PP controller card Repeat/Data field (PP Controller Card 3.2.2).  The argument specifies the total number of times the state will be executed; the minimum allowed value is 2 and the maximum is 16,777,217.  Note that the REPEAT bit in the control field must also be set for the repeat to occur.  Also note that this uses the same memory field as the ESR_data primitive so both should not occur in the same state.
     
    ESR_data(value)
    Set PP controller card Repeat/Data field (PP Controller Card 3.2.2).  The argument specifies the value that is to be loaded into an ESR register.  The minimum allowed value is 0 and the maximum is 16777215.  Note that a LOAD_ESR_n bit in the control field must also be set for a register to actually be loaded.  Also note that this uses the same memory field as the Repeat primitive so both should not occur in the same state.
     
    Call(value)
    Set PP controller card Call Address field (PP Controller Card 3.2.3). The argument is the pulse programmer memory address of a subprogram.
     
    Control(value)
    Set PP controller card Control field (PP Controller Card 3.2.4)
     
    Xmit1_amp(amp)
    Set transmitter 1 amplitude.  The amplitude of the transmitter output is proportional to the value of the argument with 0 specifying no output and 1 specifying maximum output.  A negative value causes a 180 degree phase shift.
     
    Xmit1_phase(phase)
    Set transmitter 1 phase.  The argument specifies phase in degrees.
     
    Xmit1_gate(gate)
    Set transmitter 1 gate.  Bit 0 of the argument controls the internal transmitter gate and must be set to produce output.  Bits 1 and 2 control the Gate 1 and Gate 2 outputs on the transmitter back panel. These two outputs are asserted high when their respective bits are set.  Bits 3 and 4 control the Gate 3 and Gate 4 outputs on the transmitter back panel.  These two outputs are asserted low when their respective bits are set.
     
    Xmit2_amp(amp)
    Set transmitter 2 amplitude.
     
    Xmit2_phase(phase)
    Set transmitter 2 phase.
     
    Xmit2_gate(gate)
    Set transmitter 2 gate.
     
    Xmit3_amp(amp)
    Set transmitter 3 amplitude.
     
    Xmit3_phase(phase)
    Set transmitter 3 phase.
     
    Xmit3_gate(gate)
    Set transmitter 3 gate.
     
    Xmit4_amp(amp)
    Set transmitter 3 amplitude.
     
    Xmit4_phase(phase)
    Set transmitter 3 phase.
     
    Xmit4_gate(gate)
    Set transmitter 3 gate.
     
    Freq1(freq)
    Set synthesizer 1 frequency. The argument specifies frequency in Hertz. The minimum allowed value is 100e3.  The maximum allowed value depends on the type of synthesizer that has been installed.  For a PTS160 it is 320e6.   For a PTS500 it is 1000e6.
     
    Freq2(freq)
    Set synthesizer 2 frequency.
     
    Freq3(freq)
    Set synthesizer 3 frequency.
    Freq4(freq)
    Set synthesizer 3 frequency.

    Macros

    Program macros, referred to simply as "macros", are defined in the file "macros.h" which is included by "pulse.h".

    Each macro contains one or more calls to state() with one or more primitives passed in each call. Pulse programs will typically use macros frequently and the user is free to define any additional macros required for new functionality.

    A representative macro from "macros.h" is:

    #define PULSE1(amp, phase, gates, period) \
            state( Xmit1_amp(amp), Xmit1_phase(phase),\
                   Xmit1_gate(1 | ((int) gates << 1 )), Time(period) )
    

    Each time a line such as

            PULSE1(amp, phase, gates, 100e-6);
    

    is encountered in the user's code, state() is called with the primitives defined by the macro.

    An example of a multi-state macro is FID:

    #define FID(phase, dwell, dead, size) \
            state(Rec_gate(1), Time(dead)); \
            state(Rec_gate(1), Ad_disposition(SUM_SAMPLE), Ad_pointer(PRE_RESET), \
                  Ad_phase(phase), Control(AD_STROBE), Time(dwell)); \
            state(Rec_gate(1), Ad_disposition(SUM_SAMPLE), Ad_pointer(PRE_INCR), \
                  Ad_phase(phase), Control(AD_STROBE | REPEAT), Time(dwell), \
                            Repeat(size-1)); \
            if (ad_type == 16) { \
                state(Rec_gate(1), Ad_disposition(DISCARD), Ad_pointer(NOOP), \
                      Control(AD_STROBE), Time(dwell)); \
            } else if (ad_type == 12) { \
                state(Rec_gate(1), Ad_disposition(DISCARD), Ad_pointer(NOOP), \
                      Control(AD_STROBE | REPEAT), Time(dwell), Repeat(4)); \
            }
    

    Note that each call to "state()" is a complete statement and ends with a semicolon.  However, the last call to state in a macro definition does not end with a semicolon so that the macro can be invoked in the pulse program code as if it were a typical C statement.  That is, the line that the macro name appears in ends with a semicolon so that, when the contents of the macro are substituted for the macro name, the proper number of semicolons exist.

    The current list of program macros is described below:

    DELAY(period)
    Generates one state of duration "period".
     
    PULSE1(amp, phase, gates, period)
    Generates one state that turns on transmitter 1 with amplitude "amp" and phase "phase" for duration "period". The amplitude of the transmitter output is proportional to the value of "amp" with 0 specifying no output and 1 specifying maximum output. A negative value causes a 180 degree phase shift which is in addition to that specified by "phase". The "phase" argument is specified in degrees. The "gates" argument is bit encoded with the least significant bit controlling the Gate 1 output on the back panel, the next most significant bit controlling the Gate 2 output, etc.
     
    PULSE2(amp, phase, gates, period)
    Generates one state that turns on transmitter 2 with amplitude "amp" and phase "phase" for duration "period".  See PULSE1 for details.
     
    PULSE3(amp, phase, gates, period)
    Generates one state that turns on transmitter 3 with amplitude "amp" and phase "phase" for duration "period".  See PULSE1 for details.
     
    PULSE4(amp, phase, gates, period)
    Generates one state that turns on transmitter 4 with amplitude "amp" and phase "phase" for duration "period".  See PULSE1 for details.

    LOAD_ESR(esr, number)
    Generates one state that loads the ESR register specified by "esr" with the value specified by "number". Valid ESR register are 1, 2, or 3. The minimum allowed value of "number" is 0 and the maximum is 16777215.
     
    FID(phase, dwell, dead, size)
    Generates a multiple state sequence to collect an FID.  The sequence first opens the receiver gate and waits for a period of "dead" seconds.  It then produces the number of A-D converter strobes needed to digitize "size" number of points.  The strobes are separated by time "dwell".  Each point is tagged with an instruction to the DAP to rotate it by "phase" degrees.  The macro resets the DAP buffer pointer when the first point is digitized and purges the A-D pipeline after the last point is digitized.
     
    FIDDEC2(rphase, dwell, dead, size, amp, xphase, gates)
    Generates a multiple state sequence which combines the functionality of the FID and PULSE2 macros.  The sequence of states is the same as generated by FID accept that transmitter 2 is enabled (with amplitude "amp", phase "xphase", and gating outputs "gates") during the entire sequence.  This is intended for decoupling purposes.
     
    RELAX(relax, scan_num, num_scans)
    Generates a single state of duration "relax" for implementing relaxation delays.  This state always contains a field that sends a NEXT_DISPLAY message to the DAP (tells the DAP that it is time to update the display).  It also always has the FIFO_SYNC bit set in the control field (tells the PP that this state can be extended if the state memory FIFO is becoming empty) and the CONDITIONAL_ACTION_1 bit (indicates that this state is the end of a scan).

    Additionally, if "scan_num" is equal to "num_scans" minus 1 (i.e., this is the last scan, assuming that for the first scan "scan_num" equaled zero), the state contains a field that sends a TRANSMIT_BUFFER message to the DAP (tells the DAP it is time to write the data to the output file).  It also has the CONDITIONAL_ACTION_2 bit set in the control field (indicates that this state is the end of an experiment).

     
    FID_SIZE(size)
    Generates a multiple state sequence that sends a message to the DAP conveying the size of the FID. The "size" argument specifies the number of complex points. The minimum allowable value is 1, the maximum is 131,072.
     
    AD_TYPE(type)
    Generates a multiple state sequence that sends a message to the DAP indicating which A-D converters should be used.  If "type" equals 12 the 12 bit converters are selected, if "type" equals 16 the 16 bit converters are selected.
     
    PHASE_SHIFT_DIRECTION(dir)
    Generates a multiple state sequence that sends a message to the DAP controlling the direction of receiver phase shifts specified by the Ad_phase() primitive.  If "dir" is zero, the phase shift has the normal trigonometric sense.  If "dir" is one the sense of the phase shift is negated.  This can be used to allow "cyclops" type phase rotation experiments to use the same phase list for both receiver and transmitter.
     
    PHASE_ROTATION_DIRECTION(dir)
    Generates a multiple state sequence that sends a message to the DAP that optionally inverts the apparent sign of frequencies in the detector frame of reference.  This can be used to correct experiments that would otherwise result in Fourier Transform results with peaks swapped between the negative and positive halves of the spectrum.  If "dir" equals zero no correction is applied.  If "dir" equals one the inversion is applied.  See DAP Software 1.3.2.6 for more details.
     
    WAIT_FOR_CONTROLLER(ctr_num)
    Generates a single state requesting synchronization with another controller.
     
    CLEAR_FID_BUFFER
    Generates a single state which sends a message to the DAP requesting that the FID buffer be cleared.
     
    INIT _EXPERIMENT
    Synonym for CLEAR_FID_BUFFER.
     
    LOAD_SUBPROGRAM(sub)
    Calls the function specified by "sub" in such a manner that all calls to state() made by the function load states into the subprogram RAM instead of the state memory FIFO. One additional state is generated after the function has returned; this state contains a single state with a set CALL_RETURN bit in the control field.  The macro returns the subprogram RAM address where the first state generated by "sub" was loaded.
     
    CALL_SUBPROGRAM(value)
    Generates a single state which calls the subprogram at the RAM address specified by "value".

    Symbol Table

    The external symbol table contains all the user defined parameters for a pulse program. It is implemented as a file which always has the name "symbols.sym" and is found in the users pulse program directory.  When the pulse program is executed, it reads the symbol table file to determine the values of the user defined parameters.  The user can edit the values of "symbols" in the symbol table by using the symbol editor GUI.  Symbol table values can also be edited from the command line by using the "list" command to view values and the "define" command to change them.

    The symbol table is accessed from the pulse program by using the symbol() and tsymbol() functions:

        t90    = symbol("t90",         3e-6, 1e-6, 100e-6);
        amp    = symbol("amplitude",   1.0, -1.0, 1.0);
        phase  = symbol("phase",       0.0,  0.0, 360.0);
        fname  = tsymbol("outfile",    "temp.dat");
    

    When the pulse program is called without any command line options, the symbol() function searches the symbol table file for a symbol of the name specified by the character string argument("t90", for example) and returns the value as a double float.  The tsymbol() routine is similar except it returns a pointer to a character string.

    If the pulse program is invoked with the -d option the calls to symbol() and tsymbol() do not read the symbol table file;  instead they write a new file.  In this case the three numeric arguments to symbol() are the default value for the symbol,  the minimum acceptable value and the maximum acceptable value.  The -d argument is used to build a new symbol table using the defaults specified in the symbol() and tsymbol() functions.  The "symbols.sym" file is an ASCII file that can be altered with a text editor, although this is not the intended mode of operation.  The file created by the above example would be as follows:

        *n t90  3.0e-06  1.0e-6  100.0e-6
        *n amplitude  1.0 -1.0  1.0
        *n phase  0.0  0.0  360.0
        *t outfile  temp.dat
    

    The tokens *n,*t designate the type of record, "*n" for a numeric record used by symbol() and "*t" for a text record used by tsymbol(). The order of the records in the file is not significant.


    Global Symbol Structure

    The global symbol structure contains pre-defined names of important variables and is uniform across user-written pulse programs. It is used so that the data file will have some known parameters to pass on to analysis packages. (The meaning of the symbol table symbol names is known only to the user.)  The contents of the global symbol structure must be filled in explicitly by the pulse program as in the following example:
            symbols.sw = sw;
            symbols.sf1 = sf1 + o1;
            symbols.size = size;
            symbols.scans = scans;
    
    The entire global symbol structure is initialized to zero and thus those elements that are not assigned values by the pulse program will have a value of zero.

    The global symbols are found in a structure defined in "file_utils.h":

    typedef struct global_symbols_tag {
            double sw;
            double sf1;
            double sf2;
            double sf3;
            double size;
            double scans;
            double experiment;
    } GLOBAL_SYMBOLS;

    Command Line Options

    The pulse program can be launched from the Manager, or it can executed by simply entering the name of the executable on the command line in the usual Unix fashion.  If the experiment is to be run in the normal manner no command line options are necessary.  However, the executable pulse program file will accept certain options on the command line.  When the pulse program is launched from the Manager, the command line options specified by the "RUN_OPTIONS" tag in the console configuration file are used.  The available options are:
    -d
    create a default symbol table in the file "symbols.sym".  A pulse program should be run once with this option at the beginning of a session in order to initialize the symbol table.
    -g
    print debugging output at the level of "do_SCSI_request()" without issuing SCSI commands. (e.g. No connection to the PP or DAP.)
    -g2
    print debugging output at the level of "do_SCSI_request()" as well as issuing the SCSI commands. (e.g. The experiment will be run.)
    -g3
    print debugging output without calling "do_SCSI_request()". No SCSI commands will be issued. (e.g. No connection to the PP or DAP.)
    -o
    prompt the user for permission before overwriting an existing data file.  The prompt will be suppressed if the data file name is the same as that specified by the "OVERWRITEABLE_DATA_NAME" tag in the console configuration file.  The "-o" option is usually included when the pulse program is launched from the manager, so as to protect against accidental overwriting of data files.  However, if a series of pulse programs is launched from a script, the option is problematic as it can cause the script to hang waiting for a response to the prompt.  

    Debugging Output

    When debugging options are used the output is redirected to the files: /tmp/controller_#. Each controller in a multi-threaded experiment will create one file. /tmp/controller_#

    Each SCSI command issued to the Pulse Programmer generates a line of output except "load_FIFO()" and "load_RAM()" which print out the contents of the data buffer sent to the PP by these two commands. The data buffer is printed out one data packet at a time as in the following example generated with the -g option:

    On LUN: 0 (load_FIFO) with 40 bytes of data.  (below this line) 
    
              <---    address    --->         <---  data  --->
    
    Packet    Load  Cat  Card  Offset         (hex)   (decimal)
    
        0            3     1      1           1000  =  4096
        1            3     2      0           0150  =  336
        2       1    0     0      0           0000  =  0      End of state
    
        3            2     1      0           0001  =  1
        4       1    0     0      0           0000  =  0      End of state
    
        5            2     1      0           0002  =  2
        6       1    0     0      0           0000  =  0      End of state
    
        7            2     1      0           0000  =  0
        8            2     1      6           0042  =  66
        9       1    0     0      0           0000  =  0      End of state
    
    
    When the -g3 option is used, the same information is generated but each data packet is prefaced by the name of the macro which created that data packet. -g3

    For further information on the contents of the data packet please see the document PP Software 1.1.1.11.


    Jonathan Callahan and James Gladden