Skip to content

Commit b735d4e

Browse files
Rogue Decoding (#317)
* attempt to do a C++ stream but can't get it to link properly * attempt to be more clean with CMake lists, still not linking * ease usage of ana/rogue-read.py - update MultiSample to include i-sample in output dump - bind OFStream to python so we can call the to_csv on C++ side * Apply clang-format --style=Google * fixup and validate both methods of decoding * Apply clang-format --style=Google * install rogue-decoder.py so it can be run without pflib dev --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent d4b6eb9 commit b735d4e

6 files changed

Lines changed: 377 additions & 47 deletions

File tree

CMakeLists.txt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,16 @@ if (DEFINED ENV{ROGUE_DIR})
4949
else()
5050
set(Rogue_DIR ${CMAKE_PREFIX_PATH}/lib)
5151
endif()
52-
find_package(Rogue)
52+
find_package(Rogue CONFIG)
53+
if (${Rogue_FOUND})
54+
list(REMOVE_ITEM ROGUE_LIBRARIES "PUBLIC")
55+
if (NOT TARGET Rogue::Rogue)
56+
add_library(Rogue::Rogue INTERFACE IMPORTED)
57+
set_target_properties(Rogue::Rogue PROPERTIES
58+
INTERFACE_INCLUDE_DIRECTORIES "${ROGUE_INCLUDE_DIRS}"
59+
INTERFACE_LINK_LIBRARIES "${ROGUE_LIBRARIES}")
60+
endif()
61+
endif()
5362

5463
# construct a register_maps library that the compiler can use
5564
# to lookup registers given parameter names for a chip type
@@ -149,7 +158,7 @@ target_include_directories(pflib PUBLIC
149158
"$<INSTALL_INTERFACE:include>")
150159
target_link_libraries(pflib PUBLIC yaml-cpp::yaml-cpp packing version utility logging register_maps)
151160
if (${Rogue_FOUND})
152-
target_include_directories(pflib PUBLIC ${ROGUE_INCLUDE_ONLY})
161+
target_link_libraries(pflib PUBLIC Rogue::Rogue)
153162
endif()
154163
add_dependencies(pflib direct_access lpgbt_regmap)
155164

@@ -211,6 +220,11 @@ target_link_libraries(pfdecoder PRIVATE pflib)
211220
add_executable(econd-decoder app/econd_decoder.cxx)
212221
target_link_libraries(econd-decoder PRIVATE pflib)
213222

223+
if (${Rogue_FOUND})
224+
add_executable(rogue-decoder app/rogue_decoder.cxx)
225+
target_link_libraries(rogue-decoder PUBLIC pflib Rogue::Rogue)
226+
endif()
227+
214228
add_executable(pfcompile app/pfcompile.cxx)
215229
target_link_libraries(pfcompile pflib)
216230

@@ -224,6 +238,7 @@ target_link_libraries(pfdefaults pflib)
224238
set_target_properties(pflib menu packing logging version utility register_maps PROPERTIES PREFIX "libpflib_")
225239
# remove 'lib' prefix from pypflib so we can 'import pypflib' in Python
226240
set_target_properties(pypflib PROPERTIES PREFIX "")
241+
install(PROGRAMS app/rogue-decoder.py DESTINATION bin)
227242
install(TARGETS pflib packing logging version utility register_maps pypflib pftool pfdecoder pfdecompile pfcompile pfdefaults
228243
EXPORT pflibTargets
229244
LIBRARY DESTINATION lib

ana/rogue-read.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

app/rogue-decoder.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python3
2+
import pyrogue.utilities.fileio
3+
import sys
4+
import numpy as np
5+
import pypflib
6+
import argparse
7+
from pathlib import Path
8+
9+
def main():
10+
parser = argparse.ArgumentParser()
11+
parser.add_argument('input', help='input file to Hcal/Ecal data from')
12+
parser.add_argument('--n-links', help='number of active links connected to the ECON-D', type=int, default=2)
13+
parser.add_argument('--output', '-o', help='output CSV to write to')
14+
parser.add_argument('--log-level', '-l', help='log level to print out while decoding',
15+
choices=list(pypflib.logging.level.names.keys()), default='info')
16+
parser.add_argument('--nevent', '-n', help='maximum number of events to decode', type=int)
17+
args = parser.parse_args()
18+
19+
if args.output is None:
20+
args.output = str(Path(args.input).with_suffix('.csv'))
21+
22+
pypflib.logging.open(True)
23+
pypflib.logging.set(getattr(pypflib.logging.level, args.log_level))
24+
25+
# number of links/channels enabled in the ECON-D
26+
# for HcalBackplane -> 2 channels for 1 ROC
27+
# for the EcalSMM -> complicated, depends on which layer
28+
# because some of the ROCs are not accessible
29+
ep = pypflib.packing.MultiSampleECONDEventPacket(args.n_links)
30+
31+
out = pypflib.packing.OFStream()
32+
out.open(args.output)
33+
ep.header_to_csv(out)
34+
35+
count = 0
36+
37+
# do NOT provide configChan = 255 because it does not provide a Loader to yaml.load
38+
# which fails in newer versions of Python :tada:
39+
with pyrogue.utilities.fileio.FileReader(files=sys.argv[1]) as fd:
40+
for header, data in fd.records():
41+
if data[-1] == 0x0a or header.channel != 0:
42+
# magic byte signaling config packet
43+
continue
44+
45+
# a byte from the Rogue header signals the subsystem
46+
# from slaclab/ldmx-firmware/common/tdaq/python/ldmx_tdaq/_Constants.py
47+
# both Hcal and Ecal are using the EcalSubsystem in RunControl
48+
# meaning they are both using the ID 5 right now
49+
if (data[1] != 5 and data[1] != 7):
50+
continue
51+
52+
if args.nevent is not None and count >= args.nevent:
53+
break
54+
55+
# need to convert the data into a std::vector<uint32_t>
56+
# data here is a np.ndarray('int8') so we re-view
57+
# it in our words and skip the first four words which
58+
# contain the Rogue-inserted EventHeader
59+
v = pypflib.packing.WordVector()
60+
words = data.view('uint32')
61+
v.extend(words[4:].tolist())
62+
63+
# this is the call that attempts to decode the word vector
64+
ep.from_word_vector(v)
65+
66+
# write out decoded data to the CSV file
67+
ep.to_csv(out)
68+
69+
count += 1
70+
71+
72+
pypflib.logging.close()
73+
74+
if __name__ == '__main__':
75+
main()

app/rogue_decoder.cxx

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/**
2+
* decoder for data files written by rogue
3+
*/
4+
5+
#include <iostream>
6+
7+
#include "pflib/Exception.h"
8+
#include "pflib/logging/Logging.h"
9+
#include "pflib/packing/Hex.h"
10+
#include "pflib/packing/MultiSampleECONDEventPacket.h"
11+
#include "pflib/version/Version.h"
12+
#include "rogue/GeneralError.h"
13+
#include "rogue/Helpers.h"
14+
#include "rogue/interfaces/stream/Frame.h"
15+
#include "rogue/interfaces/stream/FrameIterator.h"
16+
#include "rogue/interfaces/stream/Slave.h"
17+
#include "rogue/utilities/fileio/StreamReader.h"
18+
19+
/**
20+
* Accept frames from the rogue stream reader, filtering out non-Ecal stuff
21+
* and then write out the ECOND event packet to the CSV
22+
*/
23+
class CaloCSVWriter : public rogue::interfaces::stream::Slave {
24+
mutable pflib::logging::logger the_log_{pflib::logging::get("CaloCSVWriter")};
25+
std::ofstream output_;
26+
pflib::packing::MultiSampleECONDEventPacket ep_;
27+
28+
public:
29+
CaloCSVWriter(const std::string& filepath, int nlinks)
30+
: output_{filepath}, ep_{nlinks} {
31+
if (not output_) {
32+
PFEXCEPTION_RAISE("NoOpen", "Unable to open file '" + filepath + "'.");
33+
}
34+
35+
output_ << pflib::packing::MultiSampleECONDEventPacket::to_csv_header;
36+
}
37+
void acceptFrame(
38+
std::shared_ptr<rogue::interfaces::stream::Frame> frame) override {
39+
if (frame->getError()) {
40+
pflib_log(debug) << "Rogue header signaled error present";
41+
return;
42+
}
43+
44+
if (frame->getChannel() != 0) {
45+
pflib_log(debug) << "Frame belongs to non-data channel "
46+
<< frame->getChannel();
47+
return;
48+
}
49+
50+
// skip first byte which is meaningless i guess?
51+
auto frame_it{frame->begin() + 1};
52+
// get subsystem id
53+
uint8_t subsystem_id{0};
54+
rogue::interfaces::stream::fromFrame(frame_it, 1, &subsystem_id);
55+
// get to end of first 32-bit word, skipping contributor_id, burn_count
56+
frame_it += 2;
57+
58+
if (subsystem_id != 5 and subsystem_id != 7) {
59+
pflib_log(debug) << "Frame belongs to non-calo subsystem "
60+
<< subsystem_id;
61+
return;
62+
}
63+
64+
int frame_size{frame->end() - frame_it};
65+
pflib_log(debug) << "Frame size: " << frame_size;
66+
if (frame_size % 4 != 0) {
67+
pflib_log(error) << "Frame size " << frame_size
68+
<< " is not multiple of 4 bytes";
69+
}
70+
71+
std::vector<uint32_t> words(frame_size / 4);
72+
for (int i_word{0}; i_word < words.size(); i_word++) {
73+
rogue::interfaces::stream::fromFrame(frame_it, 4, &(words[i_word]));
74+
}
75+
76+
// first three words are not related to ECOND
77+
// 0: meaningless?
78+
// 1: timestamp
79+
// 2: timestamp
80+
ep_.from(std::span(words.begin() + 3, words.end()));
81+
ep_.to_csv(output_);
82+
}
83+
};
84+
85+
static void usage() {
86+
std::cout << "\n"
87+
" USAGE:\n"
88+
" rogue-decoder [options] NLINKS input_file.dat\n"
89+
"\n"
90+
" OPTIONS:\n"
91+
" -h,--help : print this help and exit\n"
92+
" -o,--output : output CSV file to dump samples into "
93+
"(default is input file with extension changed)\n"
94+
" -n,--nevents : provide maximum number of events (default is "
95+
"all events possible)\n"
96+
" -l,--log : logging level to printout (-1: trace up to 4: "
97+
"fatal)\n"
98+
<< std::endl;
99+
}
100+
101+
int main(int argc, char* argv[]) {
102+
pflib::logging::fixture f;
103+
104+
auto the_log_{pflib::logging::get("rogue-decoder")};
105+
106+
pflib_log(warn) << "The ECOND decoding is not well tested. Inspect the "
107+
"results carefully and please contribute!";
108+
109+
if (argc == 1) {
110+
// can't do anything without any arguments
111+
usage();
112+
return 1;
113+
}
114+
115+
int n_links{-1};
116+
int nevents{-1};
117+
std::string in_file, out_file;
118+
for (int i_arg{1}; i_arg < argc; i_arg++) {
119+
std::string arg{argv[i_arg]};
120+
if (arg[0] == '-') {
121+
// option
122+
if (arg == "-h" or arg == "--help") {
123+
usage();
124+
return 0;
125+
} else if (arg == "-o" or arg == "--output") {
126+
if (i_arg + 1 == argc or argv[i_arg + 1][0] == '-') {
127+
pflib_log(fatal) << "The " << arg
128+
<< " parameter requires an argument after it.";
129+
return 1;
130+
}
131+
i_arg++;
132+
out_file = argv[i_arg];
133+
} else if (arg == "-n" or arg == "--nevents") {
134+
if (i_arg + 1 == argc or argv[i_arg + 1][0] == '-') {
135+
pflib_log(fatal) << "The " << arg
136+
<< " parameter requires an argument after it.";
137+
return 1;
138+
}
139+
i_arg++;
140+
try {
141+
nevents = std::stoi(argv[i_arg]);
142+
} catch (const std::invalid_argument& e) {
143+
pflib_log(fatal) << "The argument to " << arg << " '" << argv[i_arg]
144+
<< "' is not an integer.";
145+
return 1;
146+
}
147+
} else if (arg == "-l" or arg == "--log") {
148+
if (i_arg + 1 == argc) {
149+
pflib_log(fatal) << "The " << arg
150+
<< " parameter requires an argument after it.";
151+
return 1;
152+
}
153+
std::string arg_p1{argv[i_arg + 1]};
154+
if (arg_p1[0] == '-' and arg_p1 != "-1") {
155+
pflib_log(fatal) << "The " << arg
156+
<< " parameter requires an argument after it.";
157+
return 1;
158+
}
159+
i_arg++;
160+
try {
161+
pflib::logging::set(pflib::logging::convert(std::stoi(argv[i_arg])));
162+
} catch (const std::invalid_argument& e) {
163+
pflib_log(fatal) << "The argument to " << arg << " '" << argv[i_arg]
164+
<< "' is not an integer.";
165+
return 1;
166+
}
167+
} else {
168+
pflib_log(fatal) << "Unrecognized option " << arg;
169+
return 1;
170+
}
171+
} else {
172+
if (n_links <= 0) {
173+
try {
174+
n_links = std::stoi(arg);
175+
} catch (const std::invalid_argument& e) {
176+
pflib_log(fatal) << "The first positional argument needs to be the "
177+
"number of links, but '"
178+
<< arg << "' is not an integer.";
179+
return 1;
180+
}
181+
} else if (not in_file.empty()) {
182+
pflib_log(fatal) << "Can only decode one file at a time.";
183+
return 1;
184+
} else {
185+
in_file = arg;
186+
}
187+
}
188+
}
189+
190+
pflib_log(debug) << pflib::version::debug();
191+
192+
if (in_file.empty()) {
193+
pflib_log(fatal) << "Need to provide a file to decode.";
194+
usage();
195+
return 1;
196+
}
197+
198+
if (out_file.empty()) {
199+
out_file = in_file.substr(0, in_file.find_last_of(".")) + ".csv";
200+
}
201+
202+
try {
203+
// setup stream
204+
auto reader = std::make_shared<rogue::utilities::fileio::StreamReader>();
205+
auto writer = std::make_shared<CaloCSVWriter>(out_file, n_links);
206+
reader->addSlave(writer);
207+
208+
// run
209+
reader->open(in_file);
210+
reader->closeWait();
211+
} catch (const pflib::Exception& e) {
212+
pflib_log(fatal) << "pflib [" << e.name() << "]: " << e.message();
213+
return 1;
214+
} catch (const rogue::GeneralError& e) {
215+
pflib_log(fatal) << "Rogue Error: " << e.what();
216+
return 1;
217+
} catch (const std::runtime_error& e) {
218+
pflib_log(fatal) << e.what();
219+
return 1;
220+
}
221+
222+
return 0;
223+
}

0 commit comments

Comments
 (0)