TOF Spectrum (Time-of-Flight Histogram)
TOF_Spectrum builds a real-time histogram of time-of-flight measurements. It measures the time between a reference signal (T0) and detector events (IN), accumulating counts into configurable time bins. Ideal for neutron scattering, mass spectrometry, and any application requiring time-of-arrival distributions.
Introduction
Principle of Operation
The TOF Spectrum block implements a 1D histogram that accumulates time-of-flight measurements in real-time on the FPGA.
Unlike the standard Spectrum block (which histograms amplitude values), the TOF Spectrum measures time intervals between:
- T0: Reference timing signal (e.g., accelerator pulse, chopper, trigger)
- IN: Detector event signal
Each time bin corresponds to a specific time window after T0:
$$ bin_{index} = \left\lfloor \frac{t_{event} - t_{T0} - T_{delay}}{T_{bin}} \right\rfloor $$
where:
- $t_{event}$ = time of detector event (IN rising edge)
- $t_{T0}$ = time of reference signal (T0 rising edge)
- $T_{delay}$ = configurable start delay
- $T_{bin}$ = configurable bin width (in clock cycles)
Pin Description
Properties
Set the name of the endpoint
Logical endpoint name used in the register map. Used by Resource Explorer and SciSDK.Default: TOF_0
Number of bins in the spectrum
Number of time bins in the histogram. Options: 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384. More bins = longer measurement window or finer resolution.Default: 1024
Options: 32 64 128 256 512 1024 2048 4096 8192 16384
Height in bits of each bin
Number of bits per bin (maximum counts per bin). Options: 16, 24, 32 bits.
| Bits | Max Counts |
|---|---|
| 16 | 65,535 |
| 24 | 16,777,215 |
| 32 | 4,294,967,295 |
Default: 24
Options: 16 24 32
Detailed Operation
Data Flow
flowchart LR
T0(["T0"]) --> TCH["Time Counter<br/>+ Hist"]
IN(["IN"]) --> TCH
CLK(["CLK"]) --> TCH
TCH --> MEM[("Memory<br/>(N bins)")] --> PC("PC")
TCH --> STATUS["STATUS"] --> RIC(["Running/Idle/Completed"])
Operation Sequence
- Software configures bin width and start delay via registers
- Software starts acquisition via CONFIG register
- On T0 rising edge:
- Time counter resets to zero
- After start delay, histogram accumulation begins
- On each IN rising edge:
- Current time bin is incremented
- Events are accumulated until bin window ends
- Time counter advances through all bins
- Process repeats on next T0
Timing Diagram
State Machine
The block implements a 7-state FSM:
| State | Code | Description |
|---|---|---|
| CLEAR_START | 000 | Begin memory clear |
| CLEARING | 001 | Clear all bins to zero |
| RESET_DONE | 111 | Ready for acquisition |
| WAIT_DELAY | 010 | Wait for start delay after T0 |
| HISTOGRAM | 011 | Active histogramming |
| SAVE_RAM | 100 | Write accumulated count to RAM |
| SAVE_RAM_2 | 101 | RAM write completion |
| SCAN_DONE | 110 | Acquisition complete |
Configuration Parameters
| Parameter | Register | Description |
|---|---|---|
| Bin Width | CONFIG_BINWIDTH | Width of each time bin in clock cycles. Minimum 3 |
| Start Delay | CONFIG_START_DELAY | Delay from T0 before starting histogram |
| Start/Stop | CONFIG[0] | 1 = Start, 0 = Stop |
| Reset | CONFIG[1] | 1 = Clear histogram (edge triggered) |
The Bin Period, Exactly
One time bin lasts exactly CONFIG_BINWIDTH clock cycles, so the bin a
detector event falls into is
$$ bin_{index} = \left\lfloor \frac{t_{event} - t_{T0} - T_{delay}}{T_{bin}} \right\rfloor $$
with every bin boundary at an exact multiple of T_bin.
Two details worth knowing:
- The last two clock cycles of every bin period are the write-pointer advance. An event arriving in those two clocks is not lost - it is carried over and counted in the next bin.
- The minimum bin width is 3 clock cycles, because that pointer advance
plus the reload of the bin counter costs 3 clocks per bin. The RTL
saturates a smaller value to 3, and
SPECTRUM_<name>_SET_PARAMETERSreturns-1without writing anything, so a program is told rather than silently given a coarser time axis than it asked for. - The last bin of a scan collects for
T_bin - 2clocks instead ofT_bin: it needs no pointer advance, so the scan ends 2 clock cycles beforeN_bins * T_bin.
Changed in 2026-10-11 and later bitstreams - READ THIS BEFORE COMPARING OLD AND NEW DATA. Earlier releases made a bin last
CONFIG_BINWIDTH + 3clock cycles, so the whole time axis, andT_windowwith it, was stretched by3 / CONFIG_BINWIDTH- +3 % at the bin width of the example below (100), +30 % at a bin width of 10. A histogram recorded with an older bitstream therefore has a time axis 3 % LONGER than the same acquisition recorded now: overlaying the two shifts every peak, and a time of flight computed from the old axis is 3 % too large. To reproduce an old acquisition exactly, programCONFIG_BINWIDTHwith the old value minus 3.
Pile-Up: How Close Two Events May Be
No event spacing loses a count. The bin memory accepts one update every 3 clock cycles, and events arriving inside that window are accumulated and written together as soon as it frees up - so two events on consecutive detected edges both count, and so do ten.
What the carry does change is which bin a very late event lands in: a count still pending when the write pointer moves on is written into the bin that is current when the memory frees up. In practice that is the next bin, i.e. the same attribution an event in the last two clocks of a bin period gets anyway. Two counts never merge and none is dropped.
A count still pending when the measurement window ends - a clear request, a new T0, the end of the scan - is discarded, because it has no bin to belong to in the next cycle.
Changed in 2026-10-11 and later bitstreams. Earlier releases lost exactly one count out of every pair of events closer than 3 clocks: the second event read the bin value back before the first event’s write had landed and overwrote it. Measured on a 600-sample test, every affected bin held 1 count instead of 2. A histogram recorded with an older bitstream is therefore under-counted wherever the instantaneous event rate was high, which biases peaks downwards exactly where the statistics matter most.
Clearing the Histogram
CONFIG[1] is edge triggered: one rising edge starts exactly one clear
pass, which walks the whole memory in N_bins + 1 clock cycles (33 clocks
for a 32-bin histogram) and then returns STATUS to 0 (Stopped) even if
the bit is still high. Holding the bit does not keep the block clearing
and does not restart the pass; the SDK SPECTRUM_<name>_RESET function
already pulses it (write 2, then 0).
While the pass runs STATUS reads 4 (Clearing) and every bin reads
zero for the whole pass, including bin 0: a host may read the histogram
during a clear and will get zeros, never a leftover value.
Changed in 2026-10-11 and later bitstreams. Before that release the clear was level triggered - holding
CONFIG[1]restarted the pass on every clock - and the first write of each pass stored the last accumulated value into bin 0 instead of zero. The final histogram was still correct (a wrap-around write repaired bin 0 at the end of the walk), so the only visible symptom was a non-zero bin 0 read back whileSTATUS = 4.
Time Resolution
The time resolution depends on the clock frequency and bin width:
$$ \Delta t = \frac{T_{bin}}{f_{CLK}} $$
| Clock | Bin Width | Time Resolution |
|---|---|---|
| 125 MHz | 3 (minimum) | 24 ns |
| 125 MHz | 10 | 80 ns |
| 125 MHz | 100 | 800 ns |
| 500 MHz | 3 (minimum) | 6 ns |
| 500 MHz | 10 | 20 ns |
Total Measurement Window
$$ T_{window} = N_{bins} \times T_{bin} / f_{CLK} $$
(exact to within the 2 clock cycles the last bin does not need for its pointer advance)
| Bins | Bin Width | Clock | Window |
|---|---|---|---|
| 1024 | 100 | 125 MHz | 819.2 µs |
| 4096 | 10 | 125 MHz | 327.7 µs |
| 8192 | 3 | 500 MHz | 49.2 µs |
Use Cases
- Neutron scattering: Time-of-flight from chopper to detector
- Mass spectrometry: Ion flight time distribution
- Particle physics: Time-of-arrival histograms
- LIDAR: Round-trip time distribution
- Fluorescence lifetime: Decay time histograms
Software Integration
The TOF Spectrum is memory-mapped and can be read via SciSDK.
Available SDK Functions
| Function | Description |
|---|---|
SPECTRUM_<name>_START |
Start acquisition |
SPECTRUM_<name>_STOP |
Stop acquisition |
SPECTRUM_<name>_RESET |
Clear histogram |
SPECTRUM_<name>_SET_PARAMETERS |
Configure bin width and start delay. Returns -1 and writes nothing if the bin width is below 3 |
SPECTRUM_<name>_STATUS |
Get acquisition status |
SPECTRUM_<name>_DOWNLOAD |
Read histogram data |
Status Values
| Value | Meaning |
|---|---|
| 0 | Stopped |
| 1 | Running |
| 2 | Idle (completed) |
| 4 | Clearing |
Python Example
python
from scisdk.scisdk import SciSDK
sdk = SciSDK()
sdk.AddNewDevice("usb:10500", "dt5560", "board0", "RegisterFile.json")
# Configure: 100 clock cycles per bin, 10 cycles start delay
sdk.SetParameter("board0:/MMCComponents/TOF_0.bin_width", 100)
sdk.SetParameter("board0:/MMCComponents/TOF_0.start_delay", 10)
# Reset and start
sdk.ExecuteCommand("board0:/MMCComponents/TOF_0.reset", "")
sdk.ExecuteCommand("board0:/MMCComponents/TOF_0.start", "")
# Wait for data...
time.sleep(10)
# Read histogram
res, data = sdk.ReadData("board0:/MMCComponents/TOF_0")
# Plot TOF spectrum
import matplotlib.pyplot as plt
plt.bar(range(len(data)), data)
plt.xlabel("Time Bin")
plt.ylabel("Counts")
plt.title("Time-of-Flight Spectrum")
plt.show()
Resources & Timing
-
Latency: 2-3 clock cycles from IN edge to bin update
-
Throughput: Any event rate: one memory update every 3 clocks, each carrying as many counts as arrived in between, so no event is ever lost to pile-up
- Uses dual-port BRAM for histogram storage
- One port for time-domain accumulation
- Second port for software readout
- Bin saturation prevents overflow wrap-around
- T0 resets time counter for each measurement cycle
- One bin lasts exactly CONFIG_BINWIDTH clock cycles, minimum 3