Struck SIS3316 digitizers
WebDAQ reads Struck SIS3316 boards (16 channels, 250 MHz 14-bit or 125 MHz
16-bit) over Ethernet next to the CAEN boards. One run can mix both: the CAEN
boards write .caendat, the SIS3316 boards write HDF5 files in the same run
directory, and the rates, spectra, alerts and auto-restart treat them alike.
The acquisition is done by sis3316daq, a pure-Python package from its own
repository, SisDAQ, pinned as a
submodule in server/native/sis3316daq and installed by install.sh. It can
also be used on its own, outside WebDAQ (see its README).
Connecting the board
The SIS3316 needs a dedicated network link: a direct cable (or a switch) on a second network interface of the DAQ PC, never the internet interface.
Give that interface a fixed address, without a gateway, e.g. with NetworkManager:
sudo nmcli con add type ethernet ifname eno1 con-name sis3316 \ ipv4.method manual ipv4.addresses 192.168.1.1/24 ipv4.never-default yes ipv6.method disabled
Map an IP address onto the board’s MAC address,
00:00:56:31:6x:xxwherexxxis the board’s serial number in hex (serial 108 →00:00:56:31:60:6c):sudo ip neigh replace 192.168.1.100 lladdr 00:00:56:31:60:6c dev eno1 nud permanent
This entry does not survive a reboot. A NetworkManager dispatcher script that runs the same command when the interface comes up makes it permanent (on the LUNA DAQ PC:
/etc/NetworkManager/dispatcher.d/90-sis3316-arp).
The board answers on UDP port 0xE000. With VME FPGA firmware before
V3316-2008 it always answers to port 0xE000 on the PC, so only one program
on the PC can talk to it: stop other SIS3316 tools while WebDAQ runs.
Adding it
On the Boards page, Add a Struck SIS3316: enter a board ID (unique among
all boards) and the IP address, then Check connection — it shows the serial
number, firmware and temperature of the board that answered — and Add
SIS3316. The board’s settings file conf/SIS3316_<id>.json is created with
defaults (all channels off) and a calibration file calib/SIS3316_<id>.cal.
Settings (DAQ page)
Selecting an SIS3316 on the DAQ page shows its own dashboard instead of the CAEN register view. Channels are numbered 0 to 15, as on the CAEN boards and the histogram pages (Struck’s manual calls them Ch1 to Ch16).
Settings works like the CAEN page: Channel enable switches channels on (nothing is recorded until one is on; All on / All off for the whole board), and Channel settings lists every setting for the Default column and each enabled channel. The Default column applies to all channels not changed themselves; a value changed for one channel is highlighted, and the arrow in its header puts it back on the defaults. Acquisition below holds the board-wide settings (sample clock, readout interval, event window, pre-trigger, waveform and trapezoid trace lengths, pileup windows, NIM input TI).
Setting |
Meaning |
|---|---|
On |
The channel triggers and records events |
Polarity |
Negative inverts the input (the board triggers on rising edges) |
Range, 50 Ω, Offset |
Input range (5 V / 2 V / 1.9 V), termination, offset DAC (32768 = centred) |
Threshold |
Trigger threshold in ADC counts of pulse height |
Trig P / G, CFD |
Fast trapezoid (trigger) peaking and gap times, constant-fraction timing |
E P / G |
Energy trapezoid peaking and flat-top times |
τ table / τ factor |
Decay correction of the energy filter (0 = off). Table 0 at 250 MHz: τ ≈ 2097 µs / factor |
Divider |
Spectrum bin = max energy / divider. With divider = energy peaking, the bin is the pulse height in ADC counts |
Saved settings apply at the next run start. During a run they reach the board at the next bank swap, except the clock and the waveform and trapezoid trace lengths, which shape the run file and are refused until the run stops.
The waveforms switch of the histogram pages turns the raw samples of an SIS3316 on (1000 samples, or the last length used) and off.
The board’s own utilities
On-board histograms — the board histograms the energy of every hit itself (64K bins, with the channel’s divider), independently of the software spectra. Read any number of bins, clear one channel or all; WebDAQ clears them at every run start.
Counters — live time, temperature and counter inputs (see Live time and scalers below), and the trigger statistic counters since the run start: all internal triggers, recorded hits, triggers lost to dead time, pileup, vetoed and high-energy-suppressed triggers. During a run they also feed the rates: Lost is the dead-time counter, and Triggers (on the histogram cards, and
triggerRatein Graphite) the internal trigger counter. After a stop the tab keeps showing the last run’s totals.Filter trace — the last recorded output of the energy (or trigger) trapezoid, for tuning peaking, gap and decay correction: with the right τ the flat top is flat. Needs a trapezoid trace length > 0.
Board info — serial number, firmware, MAC address, sample clock and temperature (Struck recommends 50 °C or less).
Run files
Each run writes data/run<N>/run_<N>_<cycle>.h5, next to the settings file
copied at the run start. The Data page lists them with the .caendat files.
They need no conversion:
import h5py, numpy as np, json
with h5py.File("data/run12/run_12_0000.h5") as f:
b = f["board_3"]
ev = b["events"][:] # one row per hit
ch14 = ev[ev["channel"] == 14] # channels are numbered 0-15, as everywhere in WebDAQ
energy = ch14["energy_max"] // 200 # // divider: the spectrum channel, as online
# (the energy calibration applies to this)
t_ns = ch14["timestamp"] * b.attrs["ns_per_sample"]
settings = json.loads(b.attrs["settings"])
waves = b["waveforms"][:10] # row-aligned with events, if recorded
Rows are written bank by bank and channel by channel, so sort by timestamp
when the order matters (coincidences).
Live time and scalers
The board counts, per channel, six trigger statistics (Struck manual 4.9), latched at every bank swap; the readout reads them once per bank anyway, so none of this costs extra network traffic:
Counter |
Counts |
|---|---|
internal |
every trigger of the channel’s internal trigger logic |
hit |
internal triggers that started an event (recorded) |
deadtime |
triggers while the board was not ready to save an event: bank swap, memory bank almost full, writing the trapezoid trace or the rest of the ADC data |
pileup |
triggers while the board was busy with an event |
veto |
triggers during a veto |
high_energy |
suppressed high-energy triggers |
Live time of a channel is 1 − deadtime / internal, the dead time
deadtime / internal. It is the share of the channel’s own triggers that
found the board ready, so it is a count, not a clock: it is the live-time
fraction of the run when the signals come at random times (source, beam),
and the share of lost pulses for a periodic pulser. It does not include:
pile-up inside an event being recorded (flagged in the event, counted as
pileup), vetoed triggers, external triggers (the internal counter does not see
them), or anything under the threshold. A channel without triggers has no
live time (shown as —), not 100 %. The counters do not restart with the run
on firmware V3316-2004, so the start latches them once straight after arming
(a bank swap, which gives up the millisecond since the arm) and counts from
there: every bank of the run is counted. Only the bank that fills while the
settings are changed during a run, or while a board recovers, is left out.
The Counters tab shows per enabled channel the live and dead time over the run, the dead time of the last stats interval, the triggers and the events read, plus the temperature (read once per stats interval), the lowest live time and the counter inputs.
Graphite gets, next to the SIS rates (<prefix> is the Graphite prefix of
the run, ancillary.rates by default):
<prefix>.bo_<id>.temperature °C
<prefix>.bo_<id>.ch_<ch>.deadFraction dead time of the last stats interval, 0-1
(not sent for an interval without triggers)
<prefix>.bo_<id>.counter.<name>.counts counter input: pulses since the run start
<prefix>.bo_<id>.counter.<name>.total ... times the scale (e.g. pC)
<prefix>.bo_<id>.counter.<name>.rate ... per second, scaled (e.g. pC/s = pA)
<name> is the counter’s name with anything but letters, digits, _ and -
replaced by _ (“Beam charge” → Beam_charge, µ → u). Two counter inputs
whose names give the same path are refused when the settings are saved.
Files in the run directory
A saved run gets, next to its .h5 files, one file for all SIS3316 boards:
sis3316_scalers.csv — every stats interval (and once more at the stop, with
the last bank), one row per board and enabled channel:
Column |
|
|---|---|
|
local time of the row (ISO) |
|
seconds since the run start |
|
board id, channel 0-15 |
|
the six counters, cumulative since the run start |
|
events read and written for the channel |
|
board temperature |
|
for a counter input: its name, |
import pandas as pd
sc = pd.read_csv("data/run12/sis3316_scalers.csv")
last = sc.groupby(["board", "channel"]).last()
live = 1 - last.deadtime / last.internal
sis3316_summary.json — written at the stop:
{"format_version": 1, "run": 12, "software": "...", "written": "...",
"counter_names": ["internal", "hit", "deadtime", "pileup", "veto", "high_energy"],
"live_time_definition": "live_fraction = 1 - deadtime / internal ...",
"boards": [{
"board_id": 3, "name": "SIS3316",
"duration_s": 3600.2, // run start to stop
"counted_s": 3600.0, // time spanned by the counted banks
"uncounted_banks": 0, // banks not in the counts (settings changed, recovery)
"temperature_c": {"last": 41.2, "min": 40.8, "max": 41.5},
"channels": [{"channel": 2, "internal": 361000, "hit": 358000, "deadtime": 3000,
"pileup": 0, "veto": 0, "high_energy": 0,
"events_written": 358020, "dead_fraction": 0.0083,
"live_fraction": 0.9917, "live_time_s": 3570.1, "integrity": "ok"}],
"counter_inputs": [{"channel": 14, "name": "Beam charge", "unit": "pC", "scale": 10.0,
"metric": "Beam_charge", "counts": 1234567, "value": 12345670.0,
"mean_rate": 3429.4}],
"integrity": {"ok": true, "mismatches": [], "tolerance": {"absolute": 2, "relative": 0.0}}
}]}
The same per board goes into every .h5 file of the run, as attributes of
/board_<id>: statistic_names, statistic_totals (16 × 6), dead_fraction
and live_fraction (16, NaN for a channel off or without triggers),
counted_s, and run_summary (the JSON above for that board).
Integrity check. At the stop the hit counter of every channel is compared
with the events actually read for it, over the same banks. They should be
equal (every bank, the last one included, is read before the board is
disarmed); when they differ by more than 2 events, the server log
warns and one line starting with [sis3316] is added to the run’s notes:
events may be missing from the files (a bank read that failed, a decoding
problem). Channels with the external trigger on, or the internal trigger
off, are not checked, since their events do not go through the hit counter.
Counter inputs
Any spare channel can count logic pulses: a current integrator’s output (beam charge), a clock, a gate. In Settings → Counter inputs, Add counter input, choose the channel, a name, the quantity per pulse and its unit (e.g. Beam charge, 10, pC). The channel must be switched on.
The count is the channel’s internal trigger counter, not the hit counter: it counts every pulse over the threshold, whether or not the board was ready to record it, and the charge delivered during dead time is still charge on target. The channel’s events are recorded as on any other channel (the energy is meaningless); at high rates they cost disk, so a few kHz is comfortable. Pulses closer together than about the trigger peaking + gap time count as one.
Cabling (the inputs are analog, 50 Ω or 1 kΩ):
NIM (fast negative logic, −0.8 V into 50 Ω): Polarity negative, 50 Ω termination on, range 2 V or 5 V, Threshold at about half the pulse: ~3000 ADC counts in the 2 V range (≈0.12 mV per count), ~1300 in the 5 V range (≈0.31 mV per count).
TTL (positive, 0/+3.3…5 V): Polarity positive. With the offset DAC centred the input spans ±1 V or ±2.5 V, so a 5 V pulse clips: use a TTL → NIM converter or a −6/−10 dB attenuator, or move the offset DAC so the baseline sits near the bottom of the range. Termination on if the source drives 50 Ω (most line drivers do; a CMOS gate output may not — then 1 kΩ). Threshold at about half the pulse at the input, in ADC counts as above.
Pulses should be at least as long as the trigger peaking time (8 samples = 32 ns at 250 MHz by default), or the trapezoid sees less than their full height. Check on the histogram page that every pulse gives one event, and compare the counter rate with the source.
On a board with VME FPGA firmware V3316-200F and ADC FPGA firmware V0250-000E or later the board’s own User Counter (manual 3.3.1.2.1 and 4.13) is the better tool: NIM input TI increments it, UI clears it, and its value is saved in every event header, so each event carries the integrated charge at its time, with no channel used up. This board’s firmware (V3316-2004 / A_0250_0004) does not have it, hence the counter-input channel.
Test mode
With TEST_FLAG=True an SIS3316 is simulated: every enabled channel sees a
pulser of fixed amplitude (with ~1 % of extra triggers counted as dead
time, so the live time is not 100 %), so adding the board, settings, runs, spectra and
files all work without hardware.