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