For the data from the FPGA we create MIDAS events using the 32-bit banks 64-bit aligned format: https://daq00.triumf.ca/MidasWiki/index.php/Event_Structure. Each bank contains time sorted 64-bit hits from the MuPix or the MuTRiG. Overall one event can hold up to 100 of these banks with the bank name HT00-HT99. The hit formats have following structure:
MuPix hit format
| Bits | Width | Field | C++ accessor equivalent | Description |
|---|---|---|---|---|
| 63 | 1 | indicator | is_pixel() |
true for MuPix |
| 62–58 | 5 | chipid | chipid() |
Global chip ID |
| 57–50 | 8 | col | col() |
Column index |
| 49–42 | 8 | row | row() |
Row index |
| 41–37 | 5 | tot / t2 | tot() / t2() |
Time-over-threshold |
| 36–16 | 21 | ts_high | time() (part) |
Timestamp high |
| 15–11 | 5 | ts_low | time() (part) |
Timestamp mid |
| 10–4 | 7 | subheader_time | time() (part) |
Subheader timing |
| 3–0 | 4 | ts_sorterhit | time() (part) |
Hit timestamp sorter |
MuTRiG hit format
| Bits | Width | Field | C++ accessor equivalent | Description |
|---|---|---|---|---|
| 63 | 1 | indicator | is_mutrig() |
true for MuTRiG |
| 62–61 | 2 | chipid | asic() |
ASIC ID |
| 60–56 | 5 | channel | channel() |
Channel ID |
| 55–47 | 9 | e-t | tot() / eflag() |
Energy or short-hit flag |
| 46–44 | 3 | time_remainder | — | 1.6 ns remainder bits |
| 43–39 | 5 | fine_time | — | Fine timestamp |
| 38–16 | 23 | ts_high | time() (part) |
Timestamp high |
| 15–12 | 4 | ts_low | time() (part) |
Timestamp mid |
| 11–4 | 8 | subheader_time | time() (part) |
Subheader timing |
| 3–0 | 4 | ts_sorterhit | time() (part) |
Hit timestamp sorter |
And in structs:
struct pixelhit {
pixelhit() noexcept : hitdata(0x0) {}
pixelhit(uint64_t h) noexcept : hitdata(h) {}
uint64_t hitdata;
[[nodiscard]] bool is_pixel() const { return ((hitdata >> 63) & 0x1) == 0; }
[[nodiscard]] uint8_t chipid() const { return (hitdata >> 58) & 0x1F; }
[[nodiscard]] uint8_t col() const { return (hitdata >> 50) & 0xFF; }
[[nodiscard]] uint8_t row() const { return (hitdata >> 42) & 0xFF; }
[[nodiscard]] uint8_t tot() const { return (hitdata >> 37) & 0x1F; }
[[nodiscard]] uint8_t t2() const { return tot(); }
[[nodiscard]] uint32_t ts_high() const { return (hitdata >> 16) & 0x1FFFFF; }
[[nodiscard]] uint8_t ts_low() const { return (hitdata >> 11) & 0x1F; }
[[nodiscard]] uint8_t subheader_time() const { return (hitdata >> 4) & 0x7F; }
[[nodiscard]] uint8_t ts_sorterhit() const { return hitdata & 0xF; }
[[nodiscard]] uint64_t time() const { return hitdata & 0x1FFFFFFFFFULL; }
void Print() const {
std::printf(
"x64:%016llx chipid:%02x col:%u row:%u tot:%u time:%010llx\n",
(unsigned long long)hitdata,
chipid(),
col(),
row(),
tot(),
(unsigned long long)time()
);
}
};
struct mutrighit {
mutrighit() noexcept : hitdata(0x0) {}
mutrighit(uint64_t h) noexcept : hitdata(h) {}
uint64_t hitdata;
[[nodiscard]] bool is_mutrig() const { return ((hitdata >> 63) & 0x1) == 1; }
[[nodiscard]] uint8_t chipid() const { return (hitdata >> 61) & 0x3; }
[[nodiscard]] uint8_t asic() const { return chipid(); }
[[nodiscard]] uint8_t channel() const { return (hitdata >> 56) & 0x1F; }
[[nodiscard]] uint16_t et() const { return (hitdata >> 47) & 0x1FF; }
[[nodiscard]] bool eflag() const { return et() == 0x1FF; }
[[nodiscard]] uint16_t tot() const { return et(); }
[[nodiscard]] uint8_t time_remainder() const { return (hitdata >> 44) & 0x7; }
[[nodiscard]] uint8_t fine_time() const { return (hitdata >> 39) & 0x1F; }
[[nodiscard]] uint32_t ts_high() const { return (hitdata >> 16) & 0x7FFFFF; }
[[nodiscard]] uint8_t ts_low() const { return (hitdata >> 12) & 0xF; }
[[nodiscard]] uint8_t subheader_time() const { return (hitdata >> 4) & 0xFF; }
[[nodiscard]] uint8_t ts_sorterhit() const { return hitdata & 0xF; }
[[nodiscard]] uint64_t time() const { return hitdata & 0x7FFFFFFFFFULL; }
void Print() const {
std::printf(
"x64:%016llx chipid:%u channel:%u et:%u rem:%u fine:%u time:%010llx\n",
(unsigned long long)hitdata,
chipid(),
channel(),
et(),
time_remainder(),
fine_time(),
(unsigned long long)time()
);
}
};
struct hit {
hit() noexcept : hitdata(0x0) {}
explicit hit(uint64_t h) noexcept : hitdata(h) {}
uint64_t hitdata;
// Common discriminator
[[nodiscard]] bool is_pixel() const { return ((hitdata >> 63) & 0x1) == 0; }
[[nodiscard]] bool is_mutrig() const { return ((hitdata >> 63) & 0x1) == 1; }
// Convert to typed views
[[nodiscard]] pixelhit as_pixel() const { return pixelhit(hitdata); }
[[nodiscard]] mutrighit as_mutrig() const { return mutrighit(hitdata); }
// helper
[[nodiscard]] uint64_t raw() const { return hitdata; }
void Print() const {
if(is_pixel()) {
as_pixel().Print();
} else {
as_mutrig().Print();
}
}
};
To extract the banks from the MIDAS event the following analyze function can be used:
TAFlowEvent* AnaFillHits::Analyze(TARunInfo* runinfo, TMEvent* event, TAFlags* flags, TAFlowEvent* flow) {
// If this module is disabled, don't do anything.
if(!enabled_) {
*flags |= TAFlag_SKIP_PROFILE;
return flow;
}
// Only process readout events
if(event->event_id != 301) return flow;
// ----------------------------------------
// Build event header
// ----------------------------------------
eventheader h;
h.event_id = event->event_id;
h.trigger_mask = event->trigger_mask;
h.serial_number = event->serial_number;
h.midas_time_stamp = event->time_stamp;
h.data_size = event->data_size;
const uint32_t sr_num = h.serial_number;
SrNo->Fill(static_cast<double>(sr_num));
// ----------------------------------------
// Scan MIDAS banks
// ----------------------------------------
event->FindAllBanks();
for(const auto& bank : event->banks) {
const std::string firstTwoChars = bank.name.substr(0, 2);
const char* rawData = event->GetBankData(&bank);
// ----------------------------------------
// HTxx bank: mixed hit bank
// ----------------------------------------
if(firstTwoChars == "HT") {
const hit* dataStart = reinterpret_cast<const hit*>(rawData);
const hit* dataEnd = reinterpret_cast<const hit*>(rawData + bank.data_size);
hits_.reserve(hits_.size() + (dataEnd - dataStart));
for(const hit* current = dataStart; current != dataEnd; ++current) {
// Optional validation / monitoring can be added here
// Example:
// if(current->is_pixel()) { auto p = current->as_pixel(); ... }
// else { auto m = current->as_mutrig(); ... }
hits_.emplace_back(*current);
}
}
}
// ----------------------------------------
// Optional monitoring of mixed hits
// ----------------------------------------
for(const auto& hhit : hits_) {
if(hhit.is_pixel()) {
const auto p = hhit.as_pixel();
timestamp->Fill(static_cast<double>(p.time()));
SrNo_ts->Fill(static_cast<double>(sr_num), static_cast<double>(p.time()));
} else {
const auto m = hhit.as_mutrig();
SrNo_ts_mutrig->Fill(static_cast<double>(sr_num), static_cast<double>(m.time()));
}
}
// ----------------------------------------
// Package hits into flow event
// ----------------------------------------
flow = new HitVectorFlowEvent(flow, h, std::move(hits_));
// After std::move, reset to default state
hits_ = std::vector<hit>();
return flow;
}