Trapezoidal Shaper HLS
Pulse-height analysis chain built around a trapezoidal shaper with integrated pole-zero deconvolution, plus a baseline restorer, an energy sampler with pile-up rejection and a time-stamp latch. Unlike the MCA blocks it contains no trigger filter: you feed it a trigger from outside. The core is compiled by Vitis HLS at build time from Resources/Code/PHA_chain.cpp.
Introduction
Principle of Operation
Trapezoidal Shaper HLS is the “everything after the trigger” half of a
spectroscopy chain. The C source (PHA_chain.cpp, top function PHAchain)
wires three stages in series, all pipelined at one sample per clock:
DATA_IN ─►[ HLSDPP_trapezio ]─┬─► TRAP MON
k, m, M, G │
├─►[ HLSDPP_baseline_restorer ]─► BL MON
│ BL_LEN, BL_INIB │
│ ▼
└────────►[ HLSDPP_energysampler ]─► ENERGY
TRIGGER ──────────(+1 clk)──────────────► SAMPLE_POS, PUR ENERGY DV
TIMETAG ────────────────────────────────► PUR_MODE, PILEUP_INIB TIMESTAMP
Where it sits among the trapezoid blocks
| Trapezoidal Shaper FP | Trapezoidal Shaper HLS (this page) | TRP MCA | MCA / MCA HLS | |
|---|---|---|---|---|
| internal name | trapezoidal |
PHAchain |
trp_trapezoidal |
mcahp / mcahp_test |
| trigger filter | none | none — external TRIGGER |
built in (TRIG_K/TRIG_M/THRS) |
built in |
| pole-zero deconvolution | yes (M, 32 bit) |
yes (DECONV_M, 24 bit) |
deliberately absent | yes |
| baseline | supplied from outside (BASELINE pin) |
computed internally + hold | computed internally + leakage model | computed internally + hold |
| energy sampler | no — raw shaper stream out | yes (SAMPLE_POS) |
yes | yes |
| pile-up rejection | no | yes (3 modes) | high-rate mode | only MCA Short Signal |
| time stamping | no | yes | yes | only MCA HLS |
| output | SHAPER_OUT 16 bit + DV |
ENERGY 32 bit + ENERGY DV |
ENERGY 32 bit |
ENERGY 32 bit |
| HLS tool-chain needed | no | yes | yes | no / yes |
So “chain” is literal: compared to Trapezoidal Shaper FP, which is a bare shaper that streams a 16-bit shaped waveform and expects you to build the rest, this block adds the baseline restorer with trigger-driven hold, the energy sampler, a three-mode pile-up rejector and a 64-bit time-stamp latch, and widens the result to 32 bits. Compared to TRP MCA it keeps the exponential deconvolution (TRP MCA drops it because a transistor-reset preamplifier has no decay constant) and drops the leakage estimator, the reset detector and the built-in trigger filter.
Use it when your trigger already exists somewhere else in the design — a Fast Shaper Trigger, a CFD, a coincidence unit or an external input — and you only need the energy branch.
Pin Description
POLARITY / OFFSET.
TRIGGER on the canvas). This block
has no internal trigger filter: without a pulse here nothing is ever
emitted. It starts the energy-sampling delay, freezes the baseline for
BL_INIB samples and drives the pile-up state machine — which watches it
as a level, so it must be a single-clock pulse.
PHA_chain.cpp. Invert negative-going pulses upstream. Unconnected it
is driven to '1'.
BIT_NUM on the canvas). Present on the
symbol and wired to the core, but not used by any stage of
PHA_chain.cpp.
PHA_chain.cpp. Subtract the pedestal upstream.
TRAP_M it sets
a trapezoid of rise $\min(K,M)$ and flat top $|K-M|$. Must be smaller than
Max shaper samples. Unconnected it is driven to x"0100" (256).
x"010A"
(266), giving a 10-sample flat top with the default TRAP_K.
DECONV_M = 256 / (exp(Ts/tau) − 1). 0 disables the deconvolution.
Unconnected it is driven to x"007C80".
0x010000 = ×1.0), applied after the fixed $2^{24}$
descaling. Unconnected it is driven to x"010000".
RUN(1) CFG(0)
is low — changing it on the fly corrupts the baseline. Unconnected it is
driven to x"8" (256 samples).
x"1000" (4096).
SAMPLE_POS + 6. Aim for the middle of the flat top.
Must be smaller than Max shaper samples − 5. Unconnected it is driven to
x"0108" (264).
SAMPLE_POS to form the
window used by PUR_MODE 1 and ≥ 2. Ignored when PUR_MODE = 0. Set it to
at least the fall time of the trapezoid.
TIMESTAMP at the
instant the energy is accepted. Leave unconnected if you do not need
time stamps.
BL_LEN. On the 0→1 edge every stage reloads its pre-roll counter,
so expect several thousand samples of dead time before the first energy.
DATA_IN sample
is consumed per rising edge; the HLS core is closed at the project
acquisition-clock period.
TRAP-BL MON — so it is only meaningful on the clock
where ENERGY DV is high. Latch it there.
ENERGY DV on the canvas). One clock high,
SAMPLE_POS + 6 samples after the trigger, for every event that survives
PUR_MODE.
BL_INIB window
after a trigger). Permanently high means BL_INIB is too long for the
current rate.
DECONV_M — a correct pole-zero gives a flat top and no undershoot
after the trapezoid.
ENERGY.
Scope it against TRIGGER to place SAMPLE_POS.
TIMETAG at the moment the energy was
accepted. Driven to 0 on every other clock, so it must be captured on
ENERGY DV.
Properties
This parameters configure the number of samples used by the trapezoidal. It impact of the RAM memory usage in the FPGA
Max shaper samples — depth of the two 18-bit shaper delay lines and of
the energy sampler’s trigger-flag buffer. It is the ceiling for TRAP_K,
TRAP_M and (minus 5) SAMPLE_POS, and it sets the shaper start-up dead
time to 2 × this value.
Allowed: 128, 256, 512, 1024, 2048, 4096, 8192, 16384.
Default: 2048
Default: 2048
Options: 128 256 512 1024 2048 4096 8192 16384
This parameters configure the number of samples used by the baseline moving average. It impact of the RAM memory usage in the FPGA
Max baseline samples — depth of the 32-bit baseline ring buffer. It is the ceiling for $2^{\rm BL_LEN}$ and sets the baseline start-up dead time to 2 × this value.
Allowed: 128, 256, 512, 1024, 2048, 4096, 8192, 16384.
Default: 2048
Default: 2048
Options: 128 256 512 1024 2048 4096 8192 16384
Size in bit of the signal accumulator. It allows to create a filter that a less logic resources. If the exponentials signal are fast and trapezoidal is short the accumulator can be configured smaller
Accumulator Size — width, in bits, of the second (deconvolution)
accumulator. This accumulator wraps, it does not saturate: too few bits
with a long trapezoid or a large DECONV_M corrupts large-energy events
silently. Reduce it below 64 only for short trapezoids, and verify the peak
positions afterwards.
Note that this value is not part of the generated HLS IP name — see the collision warning above.
Allowed: 32, 48, 64, 96.
Default: 64
Default: 64
Options: 32 48 64 96
⚙️ Detailed Operation
The shaper, exactly as the C computes it
HLSDPP_trapezio is the Jordanov–Knoll recursive trapezoid with the pole-zero
term folded into the second accumulator. Reading the source line by line, with
$k$ = TRAP_K, $m$ = TRAP_M, $M$ = DECONV_M, $G$ = TRAP_GAIN:
$$ s_1[n] = x[n] - x[n-k] $$ $$ s_2[n] = s_1[n] - s_1[n-m] $$ $$ \mathrm{acc}_1[n] = \mathrm{acc}_1[n-1] + s_2[n] $$ $$ s_3[n] = 256,\mathrm{acc}_1[n] + M,s_2[n] $$ $$ \mathrm{acc}_2[n] = \mathrm{acc}_2[n-1] + s_3[n] $$ $$ \mathrm{TRAP}[n] = \frac{\bigl(\mathrm{acc}_2[n] \gg 24\bigr)\cdot G}{2^{16}} $$
Three things follow directly from those lines and are worth knowing before you pick coefficients:
- The two delays are independent. The rise time of the trapezoid is
$\min(k,m)$ samples and the flat top is $|k-m|$ samples.
TRAP_Mis not the flat top; it is the second differentiator delay. The default pairTRAP_K = 0x0100,TRAP_M = 0x010Ais therefore a 256-sample rise with a 10-sample flat top. DECONV_Mcarries a factor 256, because the plain accumulator is shifted left by 8 before the deconvolution term is added. That is exactly why the pole-zero coefficient is $$ M = \frac{256}{e^{T_s/\tau}-1} $$ with $T_s$ the sample period and $\tau$ the preamplifier decay constant.DECONV_M = 0disables pole-zero cancellation and leaves you with a plain trapezoid on an exponential tail.- The fixed scaling is $2^{24}$ and the gain is Q8.16. The output is
divided by $2^{24}$ and then multiplied by
TRAP_GAIN/$2^{16}$, soTRAP_GAIN = 0x010000is a gain of exactly 1.0 and the useful dynamic range is set by choosingTRAP_GAINso that the largest pulse lands near the top of the 32-bit energy word (or near the top of your spectrum’s bin count).
The accumulator width is a real design parameter
acc1 is a fixed 64-bit accumulator, but acc2 — the one that carries the
$2^{24}$-scaled sum — is exactly Accumulator Size bits wide
(LARGE_ACC_BITS). It is a plain wrapping integer: nothing saturates. The
scaled sum grows with the input amplitude, with the trapezoid length and with
the deconvolution coefficient, so a 32-bit accumulator with a long trapezoid
and a large DECONV_M will silently wrap and produce a spectrum full of
nonsense at high energy. 64 bits (the default) is the safe choice; reduce it
only when the trapezoid is short and you have verified the peak position is
unchanged.
Baseline restorer
HLSDPP_baseline_restorer keeps a running sum over a window of
$2^{\rm BL_LEN}$ samples of the shaper output and reports
BL_ACCUMULATOR >> BL_LEN. On every TRIGGER it loads BL_INIB into a dead
counter, raises BL HOLD and stops feeding the window until the counter
expires, so the pulse itself never enters the average.
BL_INIB must be longer than the entire trapezoid (rise + flat top + fall) or
the pulse tail is averaged into the baseline and the peak position drifts with
count rate. If BL HOLD is high all the time, BL_INIB is too long for the
current rate and the baseline has stopped updating altogether.
Energy sampler, and the two constants hidden in it
The trigger is delayed by exactly one clock inside the shaper stage, then
used to address a PRE_LEN-deep flag buffer at an offset of
SAMPLE_POS + 5. The measured, deterministic relation is therefore
$$ t(\mathrm{ENERGY\ DV}) = t(\mathrm{TRIGGER}) + \mathrm{SAMPLE_POS} + 6 \ \text{samples} $$
(plus the fixed pipeline latency of the core). The +5 is a hard-coded
constant in the C, not a parameter: when you calibrate SAMPLE_POS against a
scope trace of TRAP-BL MON, remember you are placing the sample six samples
later than the number you write.
Pile-up rejection (PUR_MODE)
PUR_MODE |
behaviour |
|---|---|
| 0 | No rejection and no dead time. Every trigger produces one ENERGY DV, PILEUP_INIB is ignored. |
| 1 | Dead time. An accepted event blocks the next SAMPLE_POS + PILEUP_INIB samples; triggers inside that window are discarded silently. |
| ≥ 2 | Full pile-up rejection state machine. On a trigger the block waits PILEUP_INIB + SAMPLE_POS samples; if no further trigger arrives, the energy is emitted. If another trigger arrives during the wait, the event is discarded and the machine stays in a guard state, re-arming the counter on every new trigger until PILEUP_INIB + SAMPLE_POS samples pass with no trigger at all. |
Mode ≥ 2 is the one you want for spectroscopy at rate: it throws away both
members of a pile-up pair rather than distorting the spectrum with them. Note
that the state machine watches the raw TRIGGER input (not the delayed
copy), so TRIGGER must be a clean one-clock pulse — a trigger held high
for several clocks re-arms the guard on every clock and nothing is ever
emitted.
Start-up dead time
Each of the three stages holds a pre-roll counter that is loaded with twice
its buffer length whenever RUN(1) CFG(0) is low, and decremented one per
sample once running. Until it reaches zero the delay lines are written with
zeros and no energy is produced:
| stage | pre-roll after RUN_CFG rises |
|---|---|
| shaper | 2 × Max shaper samples |
| energy sampler | 2 × Max shaper samples |
| baseline restorer | 2 × Max baseline samples (only counted while BL HOLD is low) |
With the default 2048/2048 that is 4096 samples of dead time after every
RUN_CFG 0→1 edge — 33 µs at 125 MHz. Do not arm your acquisition in the same
breath as RUN_CFG.
Parameter ceilings
All four delay lines are addressed with a wrapping pointer and no range check: an offset larger than the buffer aliases onto the wrong sample instead of raising an error. From the source:
| parameter | ceiling |
|---|---|
TRAP_K, TRAP_M |
< Max shaper samples (PRE_LEN) |
SAMPLE_POS |
< Max shaper samples − 5 |
| $2^{\rm BL_LEN}$ | ≤ Max baseline samples (BASELINE_PRE_LEN) |
Build-time behaviour
ComponentInfo.UseHLS is set, so the block writes
<project>/HLS/<ip>/PHAchain.cpp (the embedded C prefixed with
#define PRE_LEN, #define BASELINE_PRE_LEN, #define LARGE_ACC_BITS)
plus an hls_compile.tcl, and Vitis HLS synthesises the IP during the build.
The clock period handed to HLS is derived from the project acquisition clock.
If no HLS tool-chain is found and the build is not a remote/cloud build,
CompileHDL fails immediately.
Typical wiring
ADC ─┬─► Fast Shaper Trigger ─► TRIGGER
└────────────────────────► DATA_IN
Timebase counter ─────────────► TIMETAG ENERGY ───┬─► List / Spectrum
Reg ─► TRAP_K TRAP_M DECONV_M TRAP_GAIN ENERGY DV ┘
Reg ─► BL_LEN BL_INIB SAMPLE_POS TIMESTAMP ─► List endpoint
Reg ─► PUR_MODE PILEUP_INIB TRAP MON / BL MON ─► Oscilloscope
Reg ─► RUN(1) CFG(0)
This block declares no memory-mapped registers (CompileMMC / CompileSDK
are empty): drive the parameter pins from Register blocks so the SciSDK can
write them, and read the results through whatever endpoint you connect
ENERGY / TIMESTAMP to.
Resources & Timing
-
Latency: The symbol declares 13 clocks. The C constrains the shaper stage with
#pragma HLS latency min=15and the top function withPIPELINE II=1; the exact RTL pipeline depth is decided by Vitis HLS for your part and clock at build time and is not fixed in the sources. The trigger-to-strobe distance is exact and tool independent: SAMPLE_POS + 6 samples. -
Throughput: 1 sample per clock (
#pragma HLS PIPELINE II=1onPHAchain)
- Memory cost scales with the properties: two 18-bit × Max shaper samples RAMs plus one 1-bit × Max shaper samples buffer in the shaper/sampler, and one 32-bit × Max baseline samples RAM in the baseline restorer.
- The core uses
ap_ctrl_none, so there is no start/done handshake — it free-runs on every clock. - Ports carry a
_Vsuffix for Vivado versions below 2019.2 and no suffix from 2019.2 on; the block handles this automatically. - Start-up dead time after
RUN(1) CFG(0)goes high is 2 × Max shaper samples (and 2 × Max baseline samples for the baseline), i.e. 4096 samples with the defaults.