Skip to content

Trigger Link from HcalBackplane on ZCU

Tom Eichlersmith edited this page Jul 24, 2026 · 14 revisions

Set Up

Hardware

The DAQ link from the HcalBackplane is connected to SFP0 and the output from the TRG (trigger) link from the HcalBackplane is connected to the input of SFP1 (below SFP0).

Image of Hcal Backplane with optical fibers connected properly Image of ZCU Quad-SFP-Cage with the optical fibers connected properly

Firmware

A small patch was necessary to the "dualtarget" ZCU firmware necessary in order to confirm that the trigger optical link clock was correctly shared with the daq optical link clock and in order to capture words from the trigger path. Make sure you are on the latest ZCU firmware listed on the firmware page.

Check Word Rates

Connecting via Link 0 with pflpgbt, you can see the "word rate" for the DAQ and TRIG links are the same (as well as the clocks).

$ ./build/pflpgbt --do 0
> OPTO
> FULLSTATUS
Olink: DAQ
Polarity -- TX: 0  RX: 0
Optical status:
  BUFFBYPASS_DONE      : 0x0001
  BUFFBYPASS_ERROR     : 0x0000
  CDR_LOCK             : 0x0deb
  CDR_STABLE           : 0x0000
  LINK_ERRORS          : 0x0003
  NOT_IN_RESET         : 0x0001
  READY                : 0x0001
  RX_RESETDONE         : 0x0001
  TX_RESETDONE         : 0x0001
Optical rates:
  AXIS_clk             : 297.148 MHz (0x488bc)
  CLOCK_40             : 37.144 MHz (0x9118)
  DAQ_LINK_CLOCK       : 297.148 MHz (0x488bc)
  DAQ_LINK_ERROR       : 0.000 MHz (0x0000)
  DAQ_LINK_WORD        : 37.143 MHz (0x9117)
  EXT_REFCLK           : 0.000 MHz (0x0000)
  GTH_REFCLK           : 297.148 MHz (0x488bc)
  RX-LINK              : 297.148 MHz (0x488bc)
  S_AXI_ACLK           : 100.000 MHz (0x186a0)
  TRIG_LINK_CLOCK      : 297.148 MHz (0x488bc)
  TRIG_LINK_ERROR      : 0.000 MHz (0x0000)
  TRIG_LINK_WORD       : 37.144 MHz (0x9118)

This is after

  • using OPTO.RESET and OPTO.LINKTRICK in order to get the DAQ link READY 1
  • applying GENERAL.STANDARD_HCAL to configure the lpGBTs appropriately
    • using a recent version of pflib is necessary here since the TRG lpGBT configuration needed to be updated! PR 415
  • opto.linktrick after using opto.choose to switch to the TRG link

Initialize ECONs

The configurations stored in pflib are examples that show necessary parts.

  • config/pftool/hcal-backplane-zcu.yaml: define the target (HcalBackplane on ZCU) and which ROCs are active
  • config/econ/econt_hcal_stc.yaml: configure algorithm, formatter buffer, and invert eTx data
    • will need to update which eRx are active according to which ROCs are active
$ ./build/pftool config/pftool/hcal-backplane-zcu.yaml
> ECON
> IECON 0
> LOAD config/econ/econd_slac_lab1_init.yaml
> IECON 1 # switch to ECON-T that has a ROC connected
> LOAD config/econ/econt_hcal_stc.yaml
> RUNMODE
> IECON 2 # can also do the other ECON-T
> LOAD config/econ/econt_hcal_stc.yaml
> RUNMODE
> EXIT

Warning

The current trigger path firmware keeps the ECON-T streams separate and only ECON-T1 is being viewed by the software. Expanding the software to look at both ECON-Ts on the HcalBackplane is not impossible, just would require a more thoughtful design of the pflib::TRIG interface.

Align ECON-T/ROC

The alignment block that handles tuning the connection between ECON-T and ROCs is the same as the ECON-D, so the task in software only needed to update the mapping.

Note

I (Tom) have observed the ECON-T/ROC alignment to be more likely to fail if the ECON-D and the ROC aren't aligned, so make sure that the ECON-D/ROC alignment has been done first.

$ ./build/pftool config/pftool/hcal-backplane-zcu.yaml
> TASKS
> SETUP
> ALIGN_PHASE_WORD
# choose IECON=1 or IECON=2 for which ECON-T has ROCs connected

Align ECON-T/lpGBT

Same comment here as above where the ECON-D/lpGBT should be aligned before attempting ECON-T/lpGBT alignment. I have also observed that it sometimes takes multiple tries of the ECON-T/lpGBT alignment before it works.

$ ./build/pftool config/pftool/hcal-backplane-zcu.yaml
> TASKS
> SETUP
> ALIGN_ECON_LPGBT
# make sure to do the right IECON as before!
> EXIT

View TRG Data Stream

The ECON-T outputs a known pattern of data when we send a link_reset_econt command to it. The 32b words should contain two copies of the 16b idle word. The 16b idle word is a 5b BX number followed by the 11b idle pattern we configure to be 0x526. The TRIG.ALIGN.READ command can decode for this pattern to make it easier for you to observe by splitting the 32b words into the BX numbers (shown in decimal) and the idle pattern (shown in hex).

If everything is aligned properly you should see an output that looks like

00 : | 10 526 10 526 | 11 526 11 526 | 12 526 12 526 | 13 526 13 526 | 14 526 14 526 | 15 526 15 526 | 00 526 00 526 | 01 526 01 526
01 : | 10 526 10 526 | 11 526 11 526 | 12 526 12 526 | 13 526 13 526 | 14 526 14 526 | 15 526 15 526 | 00 526 00 526 | 01 526 01 526
02 : | 10 526 10 526 | 11 526 11 526 | 12 526 12 526 | 13 526 13 526 | 14 526 14 526 | 15 526 15 526 | 00 526 00 526 | 01 526 01 526

Key Features:

  • Two copies of the same BX number in each 32b word (separated by |)
  • BX numbers increment by one between 32b words and are modulo 16 (the special BX number of 31 is used to distinguish orbit boundaries)
  • BX numbers are the same across links (rows)
  • idle patterns are all uniformly equal to the expected pattern
> TRIG
> ALIGN
> READ
# view eTx streams output by ECON-T

Capture TRG Path Data

Note

Make sure to update the firmware to the latest version (20260723), otherwise the capture will be buggy and the trigger algorithm non-existent!

The default configuration of the firmware has many of these firmware parameters set to zero. You must update them before you can appropriately capture TRG path data.

  • trig.pipeline: how many BX ago we should start the capture
  • trig.samples_per_l1a: number of samples to capture on each L1A
    • This should be 1 in all cases except expert debugging. If you want more trig samples, you should also collect more daq samples in which case you should use daq.setup.config to update the number of samples.
  • trig.presamples: index within the set of samples where the primary sample of interest is (i.e. where we expect "in time" to be)
    • This should be 0 for the same reasons as above.
  • trig.econid: ID number to go with this ECON-T, stored in output header (see below)

The example below is with one ROC connected to one ECON-T that outputs three links. The DAQ is configured to readout 8 samples per readout request. There was no pedestal subtraction being applied on the ROC so the STC sums that are connected to a ROC are relatively large.

 # in the TRIG menu
 > STATUS                                                                                                                                                                                                                                     
pipeline: 5                                                                                                                                                                                                                                   
econ_id : 21                                                                                                                                                                                                                                  
samples_per_l1a: 1                                                                                                                                                                                                                            
presamples: 0                                                                                                                                                                                                                                 
capture delay: 30                                                                                                                                                                                                                             
link 0 bx delay: 0                                                                                                                                                                                                                            
link 1 bx delay: 0                                                                                                                                                                                                                            
link 2 bx delay: 0
 > EXPERT
 > SW_ROR
 > EVENT_SPY
10a4040e
13004143
40005020
10080444
a2d148b0
5000d020
10080444
a2d148b0
6000d020
10080444
a2d148b0
70005020
10080444
a2d148b0
econ_id = 41 n_samples = 4
BX STC1 STC2 STC3 STC4 STC5 STC6 STC7 STC8
 4    8    8    8    8  274  278  276  278
 5    8    8    8    8  274  278  276  278
 6    8    8    8    8  274  278  276  278
 7    8    8    8    8  274  278  276  278
 > EXIT
 > ALGO
 > SPY # just to clear algo output buffer
10000403
11001001
c0000000
10000403
11001002
c0000000
10000403
11001003
c0000000
10000403
11001004
c0000000
10000403
11001005
c0000000
10000403
11001006
c0000000
10000403
11001007
c0000000
10000403
11001008
c0000000

Time-In TRG Path Capture

The trig.timein command can help you deduce the necessary settings of the trigger capture and charge injection so that the TRG path data is captured at the same time as the pulse is passing through it.

At UMN, I found the following settings were reliably working well when only one sample was being captured per readout request.

  • pipeline: 5
  • samples_per_l1a: 8
  • presamples: 1
  • charge_to_l1a: 60
  • roc.l1offset: 48

With multi-sample enabled (daq.config using more than one sample per ROR), then we only need one sample per L1A for the trigger path capture.

  • pipeline: 6
  • samples_per_l1a: 1
  • presamples: 0
  • charge_to_l1a: 59
  • roc.l1offset: 48

A successful time-in looks like the following. The sample of interest labels point at the locations where the DAQ data is high above its pedestal, the TRG sum is also high, and the algo output is triggering.

DAQ Data
     pedestal ->  charge
 i:  t-1   t  ->  t-1   t 
 0:  199  200 ->  201  203
 1:  200  201 ->  203  515 <- sample of interest
 2:  201  200 ->  515  195
 3:  200  202 ->  195  200
 4:  202  201 ->  200  202
 5:  201  201 ->  202  200
 6:  201  199 ->  200  201
 7:  199  202 ->  201  201
TRG Data
 i:  ped -> chrg
 0:    8 ->    8
 1:    8 ->  162 <- sample of interest
 2:    8 ->    8
 3:    8 ->    8
 4:    8 ->    8
 5:    8 ->    8
 6:    8 ->    8
 7:    8 ->    8
ALGO Output
       pedestal  ->    charge   
 i: highpeak trg -> highpeak trg
 0: 00000000   0 -> 00000000   0
 1: 00000000   0 -> 00000010   1 <- sample of interest
 2: 00000000   0 -> 00000000   0
 3: 00000000   0 -> 00000000   0
 4: 00000000   0 -> 00000000   0
 5: 00000000   0 -> 00000000   0
 6: 00000000   0 -> 00000000   0
 7: 00000000   0 -> 00000000   0

On the first attempt, the algo output will probably not be configured properly. At UMN, we only have ROC0 connected and ROC0 gets directed into STC6 and STC7 of ECON-T1, so the algo is configured to disable STC0-5 (by leaving their thresholds at zero) and using a small threshold above the observed (encoded) STC pedestal sum of 8.

# in TRIG.ALGO
> STATUS
veto mask for recent history: 0x00
thresholds (0 -> disable channel)
  0 : 0x00
  1 : 0x00
  2 : 0x00
  3 : 0x00
  4 : 0x00
  5 : 0x00
  6 : 0x10
  7 : 0x10
algo output available? no

You can use the TRIG.ALGO.CONFIG command to update the history veto mask and thresholds.

Self-Trigger on Calib Pulse

The TRIG.SELF_TRIG command tests to see if we can observe a charge pulse when using our rudimentary trigger algorithm as the trigger. It does this by disabling the L1A that normally follows the calib pulse command and then enabling the external trigger (along with a "single shot" prevention mechanism to avoid saturating the buffer if the algo is poorly configured).

The trigger decision now needs to be routed through the FC block, so the roc.l1offset and the pipeline need to be increased to account for this extra time. At UMN, we found adding 12 to both allowed you to observe the self-trigger calibration pulse. The self_trigger command in the trig menu allows you to test these settings until you find one that works.

  • pipeline: 17
  • roc.l1offset: 60

Self-Trigger Charge Injection Run

The previous two tests temporarily apply the maximum pedestals and thresholds onto the ROC that is being studied in order to avoid triggering on normal electronics noise. In real data collection, the ROC should be pre-configured with these pedestals and thresholds. The pedestals (CH_##.ADC_PEDESTAL or use HALFWISE_#.ADC_PEDESTAL shortcut) are subtracted from the observed value within the ROC on the TRG path while the thresholds (DIGITALHALF_#.ADC_TH) are used to suppress normal fluctuations of the pedestal.

The trigger algorithm settings need to be updated as well. At UMN, we have ROC0 connected and it is directed into STC6 and STC7, so all of the thresholds should be kept at 0 except for thresholds 6 and 7 which should be set to something above 0x8. Even with these settings, the external L1A should not be left on for too long.

You can then manually disable the L1A following the charge pulse command (expert.fast_control.calib), enable external L1As (daq.setup.enable) (without enabling the gating!) and then attempt a charge injection run (daq.charge). Be sure to check daq.status after you enable external L1As to make sure the buffer didn't immediately saturate. If your ROCs are well configured (with good pedestals and thresholds that suppress normal electronics noise) and the trigger algorithm's thresholds are set high enough to only trigger on particuarly-high pulses, then the event occupancy should stay at zero.

⚠️ This is not well studied and should not be used for normal chip tuning. It is just a way to check the operation of the trigger algorithm.

Self-Trigger on Cosmics

TBD

Clone this wiki locally