Writing a new Calibration

Writing a new calibration is relatively simple - start by adding a new python module in the ./module_testing/calibrations/ directory, with name matching the command name you want the new calibration to have. The calibration will then be called using:

dirigent <calibration_name>

Imports in this file will need to include at minimum the click and logging libraries, and likely some decorators from the module_testing.dirigent.dirigent_cli package:

import click
import logging

from module_testing.dirigent.dirigent_cli import request_calibration_context, with_calibration_context

# Initialise local logger - use logger.info(...), logger.warning(...) etc. for log/debug messages
logger = logging.getLogger(__name__)

Next, you need to add the main click command for the calibration:

@click.command('<calibration_name>', help='<help string>')
# Extra arguments/options go here
@request_calibration_context
@with_calibration_context
def <calibration_name>(context, cli_ctx, <other click options>):
    # Calibration body goes here...

If you do not need a CalibrationContext object (i.e. for instrument control, etc) you can leave away the @request_calibration_context and @with_calibration_context decorators.

Adding Configuration Parameters

Add additional configuration/command line arguments/options as click arguments and options to this command. For example, to add a mandatory module name and an optional numeric argument:

@click.command('<calibration_name>', help='<help string>')
@click.option('-n', '--numeric-arg', metavar='NUMBER', type=float, default=1.0, show_default=True,
          help='A numeric (floating-point) argument.')
@request_calibration_context
@with_calibration_context
def <calibration_name>(context, cli_ctx, module, numeric_arg):
    # Calibration body goes here...

Default values for these arguments and options should be provided. Values can then be modified either using the relevant command line flags, or by setting values for these in the [<calibration_name>] section of the dirigent.toml or config file.

You can add the following lines to the start of your calibration function to print all calibration-specific parameter values on start of calibration:

logger.info('===== <calibration_name> parameter values as configured: =====')
for param, value in cli_ctx.params.items():
    logger.info(f'{param} = {value}')
logger.info('===========================================')

Accessing Instruments

The InstrumentCluster associated with your CalibrationContext can be accessed via context.instruments. Some examples for turning on/off instruments:

# Turn only LV on/off
context.instruments.lv_on()
context.instruments.lv_off()
# Turn both LV and HV on/off in correct order
context.instruments.on()
context.instruments.off()
# Setting the voltage of the HV supply to certain level:
context.instruments.hv_set(voltage=<desired voltage>)

Further possibilities can be either seen in the icicle.instrument_cluster class or in other calibrations. Finally, don’t forget to turn off everything on completion of the calibration:

context.instruments.off()

Calling Ph2ACF/CMSITminiDAQ

The context.run_calibration(...) and context.reset_ddr_reset(...) calls are the main methods to start a CMSITminiDAQ instance - in both cases a new XTerm window will open for the calibration or DDR reset to run in:

# Example - reset DDR as first step (with all auxiliary commands too)
logger.info('-1: Resetting board')
context.instruments.lv_on()
context.run_ddr_reset()
time.sleep(1)
context.instruments.lv_off()

logger.info('0: Turning on Instruments')
context.instruments.on()

# Example - run SCurve
logger.info('1: SCurve')
context.run_calibration('scurve')

context.instruments.off()

Alternatively to calibration_context.CalibrationContext.run_calibration, there are functions prepared that change the important registers for a scan (e.g. InjType = 0 for noise scan) and execute the scan when the configurations are set. This way, not all the configurations have to be defined in the scan itself. To import these functions use:

from module_testing/config/performable_scans import *

A list of available functions can be seen in the performable_scans api

With count, a number can be printed indicating the step at which the program is currently. Note, that performable_scans.InjectionDelay() is only fully supported by a Ph2_ACF version NEWER than v4-13. This, because the TriggerConfig and CAL_EDGE_FINE_DELAY are not updated to the CMSIT_RD53A/B.txt file in older versions (this is where dirigent then copies the values from).

Another option to run scans is the performable_scans.Scan function also located in config.performable_scans.

This function will accept dictionaries for 3 different configuration sections: RD53.Settings, Global and Settings (the last section being the scan settings). It will then update the config file with these settings and run a scan. Depending on a setting write_xml, the config file will then be updated with the findings from this scan.

Usage of the Scan() function can be found in calibrations.calibrate_module:

Scan(logger, context, 'pixelalive', True, FESettings_DictB['PixelAlive'], globalSettings_DictB['PixelAlive'], HWSettings_DictB['PixelAlive_Mask'], count='00')

Note This function is especially designed to be used with dictionaries from the submodule deps.inner_tracker_tests.

Modifying CMSIT.xml or CMSIT_RD53_ROCn.txt config parameters

The current active config may be accessed via context.calibration_set.config, but must be opened using a with ...: statement to load the current config file states. The files are saved and closed on end of the with statement, and are then ready for Ph2ACF/CMSITminiDAQ to be called. For example, config changes for a noise scan:

with context.calibration_set.config as detector:
    detector.Settings.Setting('nEvents').set('1e6')
    detector.Settings.Setting('nEvtsBurst').set('1e3')
    detector.Settings.Setting('nTRIGxEvent').set(10)
    detector.Settings.Setting('INJtype').set(0)
    detector.Settings.Setting('nClkDelays').set(50)
    detector.Settings.Setting('TargetOcc').set(target_occupancy_noise)

logger.info('1: Noise')
context.run_calibration('noise')

Global settings are accessed under detector.Settings.Setting('<setting name>'), with get() and set(value) methods available.

Individual BeBoards, OpticalGroups and Hybrids may be accessed either collectively (addressing all), or individually by ID (examples only for RD53A. RD53B has similar commands without functions to enable specific front-end types (linear, differential, synchronous…):

# Enable linear frontends on all Hybrids
detector.BeBoard().OpticalGroup().Hybrid().enable_linear()

# Set Vthreshold_LIN only on all chips on all hybrids on BeBoard 0, OpticalGroup 1
detector.BeBoard(0).OpticalGroup(1).Hybrid().RD53A().Settings.set('Vthreshold_LIN', str(starting_threshold))

# Read latency on Chip with ID 6 of Hybrid 2 of all BeBoards and OpticalGroups
detector.BeBoard().OpticalGroup().Hybrid(2).RD53A(6).Settings.get('LATENCY_CONFIG')

Register overrides are accessed via the Settings property on the RD53A/RD53B chip config object, as shown above.

Register values and masks in the text config file are accessed and updated either using the RD53BConfig.patch_from_text/RD53BConfig.patch_from_xml methods to copy values, or directly using the RD53B.text_file_object of the RD53AConfig/RD53BConfig chip config object (The same functions exist for RD53A and RD53B):

# Update latency config from results of last latency calibration on all chips
detector.BeBoard().OpticalGroup().Hybrid().RD53A().patch_from_text('LATENCY_CONFIG')

# Reset TDAC mask for all chips
from module_testing.config.calibration_config import RD53TextConfig
detector.BeBoard().OpticalGroup().Hybrid().RD53A().rd53_text_config.set('TDAC', RD53TextConfig.DEFAULT_TDAC())

Default masks, mask generators, and mask operators are provided as static methods on the RD53TextConfig class, which can be imported from module_testing.config.calibration_config. This feature is currently fully supported for RD53A. For RD53B the options for mask manipulation are limited. Due to the single front-end, it is also less important.

Queueing saves

Results form Ph2_ACF scans and monitoring files are saved automatically in the save folder. By default, this folder is called Results_dirigent/<calibration name>. To prevent older runs to be overwritten, runs with the same name are moved by adding a timestamp to their folder name Results_dirigent/<calibration name>.<timestamp>.

Additional files, such as .csv files may be added through the command:

context.calibration_set.queue_script_result(<Subfolder>,<Name>,<Header of table>,<list of results>)

There is also a support for generating and saving plots automatically. For this, use CalibrationSet.queue_plot:

context.calibration_set.queue_plot(folder, name, x_data, y_data, axes, labels, errors=[])

Uploading using Felis

If the felis option in dirigent.toml is activated, the modules are automatically registered and the Ph2_ACF scans automatically call felis to make a summary. The only maual thing that is required, is to upload the results in the end. This can be done using the following command:

context.felis.do_upload()

This will, when executed ask you for your panthera credentials.