LDMX Software
ECONDEventPacket.cxx
1#include "Packing/ECONDEventPacket.h"
2
3#include <algorithm>
4#include <bitset>
5#include <string>
6
7#include "Packing/Utility/CRC.h"
8#include "Packing/Utility/Hex.h"
9#include "Packing/Utility/Mask.h"
10
12using packing::utility::MASK;
13
14namespace packing {
15
16namespace {
17
24uint32_t packSample(bool tc, bool tp, int adc_tm1, int main_sample, int toa) {
25 uint32_t msb_meas = (adc_tm1 < 0) ? 0 : adc_tm1;
26 uint32_t lsb_meas = (toa < 0) ? 0 : toa;
27 return (static_cast<uint32_t>(tc) << 31) + (static_cast<uint32_t>(tp) << 30) +
28 ((msb_meas & MASK<10>) << 20) + ((main_sample & MASK<10>) << 10) +
29 (lsb_meas & MASK<10>);
30}
31
33const std::array<const char*, 3> LINK_STAT_BIT_NAMES = {"CRC", "EBO", "HT"};
34
35} // namespace
36
38 std::span<const uint32_t> data, ECONDLinkSubpacket& link) const {
39 if (data.empty()) {
40 ldmx_log(debug) << "no words left for link sub-packet";
41 return 0;
42 }
43 ldmx_log(trace) << "link header " << Hex(data[0]);
44 link.stat_ = ((data[0] >> 29) & MASK<3>);
45 if (link.badStat()) {
46 std::bitset<3> bits = link.stat_;
47 std::string names;
48 for (std::size_t i{0}; i < 3; i++) {
49 if (!bits.test(i)) names += std::string(" ") + LINK_STAT_BIT_NAMES[i];
50 }
51 ldmx_log(debug) << "bad link Stat bits 0b" << bits << " -> [" << names
52 << " ] mismatched";
53 }
54 link.hamming_ = ((data[0] >> 26) & MASK<3>);
55 link.is_empty_ = ((data[0] >> 25) & MASK<1>) == 1;
56 link.adc_cm0_ = ((data[0] >> 15) & MASK<10>);
57 link.adc_cm1_ = ((data[0] >> 5) & MASK<10>);
58 ldmx_log(trace) << " is_empty=" << link.is_empty_
59 << " CM0=" << link.adc_cm0_ << " CM1=" << link.adc_cm1_;
60
61 if (link.is_empty_) {
62 // F=1: single-word header, no channel map or channel data
63 // E=1 means unmasked Stat error bits caused the suppression
64 link.error_caused_empty_ = ((data[0] >> 4) & MASK<1>) == 1;
65 ldmx_log(trace) << "is empty, E=" << link.error_caused_empty_;
66 return 1;
67 }
68
69 if (data.size() < 2) {
70 ldmx_log(debug) << "link sub-packet missing channel map";
71 return 0;
72 }
73 ldmx_log(trace) << "chan map lower 32 " << Hex(data[1]);
74 link.channel_map_ =
75 ((data[0] & MASK<5>) << 32) | static_cast<uint64_t>(data[1]);
76 ldmx_log(trace) << "is not empty, channel_map="
77 << std::bitset<37>(link.channel_map_);
78
79 // two header words (including channel map)
80 std::size_t length{2};
81
82 int bits_left_in_current_word{32};
83 for (std::size_t i_chan{0}; i_chan < 37; i_chan++) {
84 // calib is the 19th channel in the sub-packet
85 uint32_t* ch = &(link.calib_);
86 if (i_chan < 18) {
87 ch = &(link.channels_[i_chan]);
88 } else if (i_chan > 18) {
89 ch = &(link.channels_[i_chan - 1]);
90 }
91
92 if (passthrough_) {
93 // channels are transparently copied from the link
94 if (length >= data.size()) {
95 ldmx_log(debug) << "pass through sub-packet ran out of words at ch "
96 << i_chan;
97 return 0;
98 }
99 *ch = data[length];
100 length++;
101 continue;
102 }
103
104 // skip channels that are not passed
105 if (((link.channel_map_ >> i_chan) & 1) == 0) {
106 *ch = 0;
107 continue;
108 }
109
110 if (length >= data.size()) {
111 ldmx_log(debug) << "sub-packet ran out of words at ch " << i_chan;
112 return 0;
113 }
114
115 // the code cannot span words, the format is byte aligned
116 auto code{(data[length] >> (bits_left_in_current_word - 4)) & MASK<4>};
117 // direct transcription of the table in Fig 20 of the ECOND Spec
118 int nbits{32}, extra{0};
119 bool has_adctm1{true}, has_toa{true};
120 bool tc{false}, tp{false};
121 if (code == 0b0000) {
122 // TOA ZS
123 has_toa = false;
124 nbits = 24;
125 } else if (code == 0b0001) {
126 // ADC(-1) and TOA ZS
127 has_adctm1 = false;
128 has_toa = false;
129 extra = 2;
130 nbits = 16;
131 } else if (code == 0b0010) {
132 // No ZS or ...
133 tp = true;
134 has_toa = false;
135 nbits = 24;
136 } else if (code == 0b0011) {
137 // ADC(-1) ZS
138 has_adctm1 = false;
139 nbits = 24;
140 } else if ((code >> 2) == 0b01) {
141 // full pass ZS in ADC mode
142 } else if ((code >> 2) == 0b11) {
143 // pass ZS because in TOT mode
144 tc = true;
145 tp = true;
146 } else {
147 // 0b10: known invalid
148 ldmx_log(debug) << "ECOND eRx known invalid code "
149 << std::bitset<2>(code >> 2);
150 tc = true;
151 }
152
153 // get the channel data from the current and perhaps the next word
154 uint64_t chan_data{0};
155 if (bits_left_in_current_word < nbits) {
156 int runon_length = nbits - bits_left_in_current_word;
157 if (length + 1 >= data.size()) {
158 ldmx_log(debug) << "sub-packet ran out of words at ch " << i_chan;
159 return 0;
160 }
161 chan_data = ((data[length] & ((1ul << bits_left_in_current_word) - 1ul))
162 << runon_length);
163 length++;
164 bits_left_in_current_word = 32;
165 chan_data +=
166 ((data[length] >> (bits_left_in_current_word - runon_length)) &
167 ((1ul << runon_length) - 1ul));
168 bits_left_in_current_word -= runon_length;
169 } else {
170 chan_data = ((data[length] >> (bits_left_in_current_word - nbits)) &
171 ((1ul << nbits) - 1ul));
172 bits_left_in_current_word -= nbits;
173 }
174
175 // measurements that do not exist are negative
176 int adc_tm1{-1}, toa{-1};
177 chan_data >>= extra;
178 if (has_toa) {
179 toa = (chan_data & MASK<10>);
180 chan_data >>= 10;
181 }
182 int main_sample = (chan_data & MASK<10>);
183 chan_data >>= 10;
184 if (has_adctm1) {
185 adc_tm1 = (chan_data & MASK<10>);
186 }
187 ldmx_log(trace) << " ch " << i_chan << " code=" << std::bitset<4>(code)
188 << " ADC(t-1)=" << adc_tm1 << " ADC/TOT=" << main_sample
189 << " TOA=" << toa;
190
191 *ch = packSample(tc, tp, adc_tm1, main_sample, toa);
192
193 if (bits_left_in_current_word == 0) {
194 length++;
195 bits_left_in_current_word = 32;
196 }
197 }
198
199 // sub-packets are 32b-word-aligned
200 if (bits_left_in_current_word < 32) {
201 length++;
202 }
203
204 if (length > data.size()) {
205 ldmx_log(debug) << "sub-packet length " << length << " exceeds "
206 << data.size() << " remaining words";
207 return 0;
208 }
209
210 ldmx_log(trace) << "eRx final length " << length;
211 return length;
212}
213
214ECONDEventPacket::ECONDEventPacket(std::size_t n_links) : links_(n_links) {}
215
217 return std::any_of(corruption_.begin(), corruption_.end(),
218 [](bool c) { return c; });
219}
220
221bool ECONDEventPacket::from(std::span<const uint32_t> data) {
222 corruption_.fill(false);
223 std::fill(links_.begin(), links_.end(), ECONDLinkSubpacket{});
224 payload_length_ = 0;
225
226 if (data.size() < 2) {
227 ldmx_log(debug) << "packet of " << data.size()
228 << " words is too short for the two header words";
229 corruption_[BAD_LENGTH] = true;
230 if (data.empty()) return false;
231 }
232
233 ldmx_log(trace) << "econd header one: " << Hex(data[0]);
234 header_marker_ = ((data[0] >> 23) & MASK<9>);
236 (header_marker_ != HEADER_MARKER_CMS);
238 // most likely not an ECON-D header at all
239 ldmx_log(debug) << "Bad header marker from ECOND 0x" << std::hex
240 << (header_marker_ >> 1) << " + 0b"
241 << (header_marker_ & 0b1) << std::dec << " in word "
242 << Hex(data[0]);
243 return false;
244 }
245 if (corruption_[BAD_LENGTH]) return false;
246
247 payload_length_ = ((data[0] >> 14) & MASK<9>);
248 passthrough_ = ((data[0] >> 13) & MASK<1>) == 1;
249 expected_ = ((data[0] >> 12) & MASK<1>) == 1;
250 ht_ = ((data[0] >> 10) & MASK<2>);
251 ebo_ = ((data[0] >> 8) & MASK<2>);
252 match_ = ((data[0] >> 7) & MASK<1>) == 1;
253 truncated_ = ((data[0] >> 6) & MASK<1>) == 1;
254 hamming_ = (data[0] & MASK<6>);
255
256 ldmx_log(trace) << "econd header two: " << Hex(data[1]);
257 bx_ = ((data[1] >> 20) & MASK<12>);
258 l1a_ = ((data[1] >> 14) & MASK<6>);
259 orbit_ = ((data[1] >> 11) & MASK<3>);
260 subpacket_error_ = ((data[1] >> 10) & MASK<1>) == 1;
261 reset_request_ = ((data[1] >> 8) & MASK<2>);
262 crc8_ = (data[1] & MASK<8>);
263 ldmx_log(trace) << " length=" << payload_length_ << " P=" << passthrough_
264 << " BX=" << bx_ << " L1A=" << l1a_ << " Orb=" << orbit_;
265
266 corruption_[BAD_LENGTH] = (data.size() != (payload_length_ + 2));
267 if (corruption_[BAD_LENGTH]) {
268 ldmx_log(debug) << "stored payload length " << payload_length_
269 << " does not match the packet length (" << data.size()
270 << " - 2)";
271 if (data.size() < (payload_length_ + 2)) return false;
272 }
273
274 // 8b CRC over both header words without the Hamming and the CRC
275 uint64_t header_crc_base = (data[0] >> 6);
276 header_crc_base <<= 30;
277 header_crc_base |= (data[1] >> 8);
278 uint8_t header_crc_val = utility::econdCrc8(header_crc_base);
279 corruption_[BAD_HEADER_CRC] = (header_crc_val != crc8_);
281 ldmx_log(debug) << "Event header 8b CRC does not match transmitted value: "
282 << std::bitset<8>(header_crc_val)
283 << " != " << std::bitset<8>(crc8_);
284 }
285
286 if (isEmpty()) {
287 ldmx_log(trace) << "empty packet, no link sub-packets";
288 return not isCorrupt();
289 }
290
291 // the last payload word is the CRC over all link sub-packets
292 auto payload{data.subspan(2, payload_length_)};
293 auto subpackets{payload.first(payload_length_ - 1)};
294 uint32_t crc_val = utility::crc32(subpackets);
295 uint32_t target = payload.back();
296 corruption_[BAD_CRC32] = (crc_val != target);
297 if (corruption_[BAD_CRC32]) {
298 ldmx_log(debug)
299 << "CRC over all link sub-packets does not match transmitted value "
300 << Hex(crc_val) << " != " << Hex(target);
301 }
302
303 if (corruption_[BAD_HEADER_CRC]) return false;
304
305 if (truncated_) {
306 ldmx_log(debug) << "Buffer control detected insufficient space so it did "
307 "not transfer link packets.";
308 corruption_[TRUNCATED] = true;
309 return false;
310 }
311
312 std::size_t offset{0};
313 for (auto& link : links_) {
314 auto length = unpackLinkSubpacket(subpackets.subspan(offset), link);
315 if (length == 0) {
316 corruption_[BAD_PAYLOAD] = true;
317 return false;
318 }
319 offset += length;
320 }
321
322 if (offset != subpackets.size()) {
323 ldmx_log(debug) << "link sub-packets end at " << offset
324 << " but the CRC word is at " << subpackets.size();
325 corruption_[BAD_PAYLOAD] = true;
326 }
327
328 return not isCorrupt();
329}
330
331} // namespace packing
std::array< bool, N_CORRUPTION > corruption_
corruption flags indexed by Corruption
bool subpacket_error_
S: error in a link sub-packet.
uint32_t payload_length_
payload length in 32b words (not including the two header words)
std::vector< ECONDLinkSubpacket > links_
storage of unpacked link data
uint32_t ht_
H/T: status of HGCROC header/trailer reconstruction.
bool passthrough_
P: pass through mode.
bool match_
M: transmitted E/B/O matches the L1A FIFO.
bool truncated_
T: packet truncated for buffer overflow.
uint32_t reset_request_
RR: reset request.
bool isEmpty() const
payload length of zero, no link sub-packets
uint32_t header_marker_
9b header marker
bool isCorrupt() const
any of the corruption flags are set
@ TRUNCATED
T flag set, link sub-packets not transmitted.
@ BAD_HEADER_CRC
8b header CRC mismatch
@ BAD_HEADER_MARKER
9b header marker is not the ECOND spec or common CMS config
@ BAD_PAYLOAD
link sub-packets do not fit or do not end at the CRC word
@ BAD_LENGTH
stored payload length does not equal packet length
@ BAD_CRC32
32b sub-packet CRC mismatch
std::size_t unpackLinkSubpacket(std::span< const uint32_t > data, ECONDLinkSubpacket &link) const
Unpack a eRx ("link") sub-packet.
bool from(std::span< const uint32_t > data)
parse into the packet from the passed data span
uint32_t hamming_
6b Hamming code of the header
uint32_t ebo_
E/B/O: status of event/BX/orbit reconstruction.
uint32_t crc8_
transmitted 8b header CRC
static const uint32_t HEADER_MARKER_SPEC
spec default and common CMS header markers (8b marker + 0)
uint32_t l1a_
6b L1A (event) number
bool expected_
E: HGCROC event header/trailer expected.
uint32_t bx_
bunch crossing
ECONDEventPacket(std::size_t n_links)
construct an event packet with the number of eRx "links"
uint32_t orbit_
3b orbit number
Data from a single eRx ("link") sub-packet of an ECON-D event packet.
bool error_caused_empty_
E flag of an empty sub-packet: suppressed due to unmasked Stat errors.
bool badStat() const
any of the three Stat bits are bad (not 1)
std::array< uint32_t, 36 > channels_
sample words of the 36 normal channels
uint32_t hamming_
3-bit Hamming code of the sub-packet header
uint32_t calib_
sample word of the calibration channel
int adc_cm1_
ADC readout of common mode 1.
uint64_t channel_map_
37-bit channel map (bit 18 is the calibration channel)
bool is_empty_
F flag: sub-packet is a single header word without channel data.
int adc_cm0_
ADC readout of common mode 0.
uint32_t stat_
3-bit Stat field (HT, EBO, CRC match), all ones is good