Belle II Software development
b2vcd_48.py
1
8
9# =============================== b2vcd ===============================
10# Convert Belle2Link data into human-readable Value Change Dump file
11# =====================================================================
12
13import pickle
14import re
15from writer import VCDWriter
16from bitstring import BitArray
17import sys
18import itertools
19import ast
20import operator as op
21
22if sys.version_info[0] < 3:
23 from itertools import izip as zip
24period = 1 # data clock period is 32 (ns), but for the sake of convenience, let's use 1
25
26# checking whether all dummies don't contain any data. Turn 'False' to speed up
27dummycheck = True
28
29
30def printBin(evt, wordwidth=8, linewidth=8, paraheight=4):
31 words = [evt[word:word + wordwidth].bin for word in range(0, len(evt), wordwidth)]
32 lines = ([' '.join(words[n:n + linewidth]) for n in range(0, len(words), linewidth)])
33 paras = (['\n'.join(lines[n:n + paraheight]) for n in range(0, len(lines), paraheight)])
34 print('\n\n'.join(paras))
35
36
37def printHex(evt, wordwidth=32, linewidth=8, paraheight=4):
38 words = [evt[word:word + wordwidth].hex for word in range(0, len(evt), wordwidth)]
39 lines = ([' '.join(words[n:n + linewidth]) for n in range(0, len(words), linewidth)])
40 paras = (['\n'.join(lines[n:n + paraheight]) for n in range(0, len(lines), paraheight)])
41 print('\n\n'.join(paras))
42
43
44# example signal assignments
45signalstsf2 = """
46dddd(15 downto 0) & cntr125M(15 downto 0) &
47hitMapTsf(191 downto 0) & valid_tracker(0 downto 0) &
48unamed(23 downto 7) &
49tracker_out[0](428 downto 210) & ccSelf(8 downto 0) &
50unamed (77 downto 36) &
51mergers[5](255 downto 236) & mergers[5](235 downto 0) &
52mergers[4](255 downto 236) & mergers[4](235 downto 0) &
53mergers[3](255 downto 236) & mergers[3](235 downto 0) &
54mergers[2](255 downto 236) & mergers[2](235 downto 0) &
55mergers[1](255 downto 236) & mergers[1](235 downto 0) &
56mergers[0](255 downto 236) & mergers[0](235 downto 0)
57"""
58
59signalsnk = """
60ddd(15 downto 0) & cntr125M2D(15 downto 0) &
61"000" & TSF0_input(218 downto 210) &
62"000" & TSF2_input(218 downto 210) &
63"000" & TSF4_input(218 downto 210) &
64"000" & TSF6_input(218 downto 210) &
65"000" & TSF8_input(218 downto 210) &
66"0000" &
67""" + \
68 ''.join([f"""TSF{sl:d}_input({h:d} downto {h - 7:d}) &
69TSF{sl:d}_input({h - 8:d} downto {h - 20:d}) &
70""" for sl in range(0, 9, 2) for h in range(209, 0, -21)]) + \
71 """unamed(901 downto 0)
72"""
73
74signalsall = signalsnk + signalstsf2
75
76# supported operators
77operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
78 ast.Div: op.truediv, ast.Pow: op.pow, ast.USub: op.neg}
79
80
81def eval_expr(expr):
82 return eval_(ast.parse(expr, mode='eval').body)
83
84
85def eval_(node):
86 if isinstance(node, ast.Num): # <number>
87 return node.n
88 elif isinstance(node, ast.BinOp): # <left> <operator> <right>
89 return operators[type(node.op)](eval_(node.left), eval_(node.right))
90 elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
91 return operators[type(node.op)](eval_(node.operand))
92 else:
93 raise TypeError(node)
94
95
96def makeAtlas(signals, evtsize):
97 """
98 Make bitmap from VHDL signal assignments.
99 input: string containing b2l signal designations.
100 output: list of lists
101 [ [ name, start, stop, pos, size, module ], ... ]
102 """
103 atlas = []
104 pos = 0
105 finesses = re.findall(r'(.+?)<=(.+?);', signals, re.DOTALL)
106 if len(finesses) != sum(1 if width > 0 else 0 for width in evtsize):
107 raise Exception('Number of valid signal assignments does not match HSLB data dimension: ' + str(evtsize))
108 for finesse in finesses:
109 for item in re.split(r'[\s\n]?&[\s\n]*', finesse[1].strip()):
110 item = re.sub(r'--.+', '', item)
111 dummy = re.match(r'"([01]+)"', item)
112 if not dummy:
113 sig = re.match(r'(.+)\s*\‍((.+) downto (.+)\‍)', item.strip())
114 if not sig:
115 print('continuing')
116 continue
117 print('signal: ' + item.strip())
118 start = eval_expr(sig.group(2))
119 stop = eval_expr(sig.group(3))
120 size = start - stop + 1
121 if size < 1:
122 raise ValueError('Vector size cannot be 0 or negative.')
123 name = sig.group(1)
124 else:
125 size = len(dummy.group(1))
126 name = 'unamed'
127 start, stop = size - 1, 0
128 atlas.append([name, start, stop, pos, size, finesse[0]])
129 pos += size
130 if pos != sum(evtsize):
131 raise ValueError(
132 f'Size of atlas:{pos} does not match event size:{evtsize}')
133 return atlas
134
135
136def isUnamed(signal):
137 return signal[0] == "unamed"
138
139
140def unpack(meta, headers, triggers, atlas, writer, evtsize):
141 """
142 transform into VCD signals from clock-seperated data and hitmap
143 """
144 unpackwith = ', '.join([f'bin:{sig[4]}' for sig in atlas])
145 vcdVars = [writer.register_var(
146 sig[5], sig[0] + f'[{sig[1]}:{sig[2]}]', 'wire', size=sig[4]) if
147 not isUnamed(sig) else None for sig in atlas]
148 event = writer.register_var('m', 'event', 'event')
149 eventNo = writer.register_var('m', 'eventNo', 'integer')
150 subrun = writer.register_var('m', 'subrun', 'integer')
151 run = writer.register_var('m', 'run', 'integer')
152 delays = [writer.register_var('m', f'delay{i}', 'wire', size=9)
153 for i in range(len(evtsize))]
154 lastvalues = [None] * len(atlas)
155 iteration = 0
156 timestamp = 0
157 power = period.bit_length() - 1 # (marginal and ugly) speed optimization
158 print('converting to waveform ...')
159
160 # null = BitArray([0] * sum(evtsize))
161
162 change = writer.change
163 for trgInd, trg in enumerate(triggers):
164 # skip empty (dummy) buffers
165 # if trg[:16].hex == 'dddd':
166 # continue
167 # print(trgInd)
168 # printHex(trg[0])
169 # printHex(trg)
170
171 timestamp = iteration << power
172 writer.change(event, timestamp, '1')
173 # writer.change(eventNo, timestamp, trgInd)
174 writer.change(eventNo, timestamp, meta[trgInd][0])
175 writer.change(run, timestamp, meta[trgInd][1])
176 writer.change(subrun, timestamp, meta[trgInd][2])
177 for i, d in enumerate(delays):
178 if len(headers[trgInd][i]) <= 0:
179 continue
180 writer.change(d, timestamp, headers[trgInd][i][64 + 11: 64 + 20].uint)
181 clocks = list(trg.cut(sum(evtsize)))
182 # recover clock shifts
183 if len(clocks) > 47:
184 clocks = clocks[1:48] + [clocks[0]]
185 # clocks = clocks[2:48] + clocks[0:2]
186 # for clock in trg.cut(sum(evtsize)):
187 for clock in clocks:
188 if timestamp & 1023 == 0:
189 print(f'\r{str(timestamp)[:-3]} us converted', end="")
190 sys.stdout.flush()
191 timestamp = iteration << power
192 iteration += 1
193 values = clock.unpack(unpackwith)
194 for i, sig in enumerate(atlas):
195 if isUnamed(sig):
196 if dummycheck and '1' in values[i] and '0' in values[i]:
197 print(f'[Warning] non-uniform dummy value detected at {timestamp}ns: {sig[3] % sum(evtsize)}')
198 print(values[i])
199 continue
200 if values[i] != lastvalues[i]:
201 change(vcdVars[i], timestamp, values[i])
202 lastvalues = values
203
204 # filling 16clk gaps
205 # timestamp = iteration << power
206 # iteration += 6
207 # values = null.unpack(unpackwith)
208 # for i, sig in enumerate(atlas):
209 # if isUnamed(sig):
210 # continue
211 # writer.change(vcdVars[i], timestamp, values[i])
212 print('')
213
214
215def writeVCD(meta, data, atlas, fname, evtsize, combine=False):
216 """
217 evt: 2D list of HSLB buffers
218 atlas: list of iterates
219 """
220 # combine data in different buffers into a single block of event size
221 samples = len(list(filter(None, data[0]))[0]) // list(filter(None, evtsize))[0]
222 NullArray = BitArray([])
223 # clocks = BitArray([])
224 clocks = []
225 headers = []
226 for evt in data:
227 clocks.append(BitArray([]).join(
228 itertools.chain.from_iterable(zip(
229 # skip b2l header (3 words)
230 *[evt[buf][32 * 3:].cut(evtsize[buf])
231 if evtsize[buf] > 0 else [NullArray] * samples
232 # for buf in range(4)]))))
233 for buf in range(len(evtsize))]))))
234 headers.append([evt[buf][:32 * 3] for buf in range(len(evtsize))])
235 with open(fname, 'w') as fout:
236 with VCDWriter(fout, timescale='1 ns', date='today') as writer:
237 if combine:
238 # combVCD(clocks, atlas, writer)
239 raise Exception('Combine has not been updated. Use literal instead.')
240 else:
241 # literalVCD(clocks, atlas, writer)
242 unpack(meta, headers, clocks, atlas, writer, evtsize)
243
244
245if __name__ == "__main__":
246 import argparse
247 parser = argparse.ArgumentParser()
248 parser.add_argument("-i", "--pickle", help="input pickle file")
249 parser.add_argument("-s", "--signal", help="input signal file")
250 parser.add_argument("-o", "--output", help="output VCD file")
251 parser.add_argument("-nc", "--nocheck", help="disable dummy variable check",
252 action="store_true")
253 args = parser.parse_args()
254 if args.signal:
255 with open(args.signal) as fin:
256 evtsize = [int(width) for width in fin.readline().split()]
257 print('interpreting B2L data with dimension ' + str(evtsize))
258 atlas = makeAtlas(fin.read(), evtsize)
259 else:
260 atlas = makeAtlas(signalsall, evtsize)
261
262 pica = args.pickle
263 rfp = open(pica, 'rb')
264 data = pickle.load(rfp)
265 meta = pickle.load(rfp)
266 rfp.close()
267
268 if args.nocheck:
269 dummycheck = False
270 fname = args.output if args.output else pica[:pica.rfind('.')] + '.vcd'
271 writeVCD(meta, data, atlas, fname, evtsize)
Definition: unpack.py:1