Belle II Software  release-05-01-25
b2vcd.py
1 # python >= 2.7
2 
3 # =============================== b2vcd ===============================
4 # Convert Belle2Link data into human-readable Value Change Dump file
5 # =====================================================================
6 
7 # 2017 Belle II Collaboration
8 # author: Tzu-An Sheng
9 # tasheng@hep1.phys.ntu.edu.tw
10 
11 
12 from __future__ import print_function
13 import pickle
14 import re
15 from writer import VCDWriter
16 from bitstring import BitArray
17 import sys
18 from operator import itemgetter
19 import itertools
20 import ast
21 import operator as op
22 
23 if sys.version_info[0] < 3:
24  from itertools import izip as zip
25 evtsize = [2048, 2048, 0, 0] # example B2l data width of each FINESSE (HSLB)
26 period = 32 # data clock period (ns)
27 
28 # checking whether all dummies don't contain any data. Turn 'False' to speed up
29 dummycheck = True
30 
31 
32 def 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 
39 def 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
47 signalstsf2 = """
48 dddd(15 downto 0) & cntr125M(15 downto 0) &
49 hitMapTsf(191 downto 0) & valid_tracker(0 downto 0) &
50 unamed(23 downto 7) &
51 tracker_out[0](428 downto 210) & ccSelf(8 downto 0) &
52 unamed (77 downto 36) &
53 mergers[5](255 downto 236) & mergers[5](235 downto 0) &
54 mergers[4](255 downto 236) & mergers[4](235 downto 0) &
55 mergers[3](255 downto 236) & mergers[3](235 downto 0) &
56 mergers[2](255 downto 236) & mergers[2](235 downto 0) &
57 mergers[1](255 downto 236) & mergers[1](235 downto 0) &
58 mergers[0](255 downto 236) & mergers[0](235 downto 0)
59 """
60 
61 signalsnk = """
62 ddd(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(["""TSF{sl:d}_input({high:d} downto {low:d}) &
71 TSF{sl:d}_input({high2:d} downto {low2:d}) &
72 """.format(sl=sl, high=h, low=h - 7, high2=h - 8, low2=h - 20) for sl in range(0, 9, 2) for h in range(209, 0, -21)]) + \
73  """unamed(901 downto 0)
74 """
75 
76 signalsall = signalsnk + signalstsf2
77 
78 
79 # supported operators
80 operators = {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 
84 def eval_expr(expr):
85  return eval_(ast.parse(expr, mode='eval').body)
86 
87 
88 def 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 
99 def 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  'Size of atlas:{} does not match event size:{}'.format(pos, evtsize))
133  return atlas
134 
135 
136 def isUnamed(signal):
137  return signal[0] == "unamed"
138 
139 
140 def unpack(triggers, atlas, writer):
141  """
142  transform into VCD signals from clock-seperated data and hitmap
143  """
144  unpackwith = ', '.join(['bin:{}'.format(sig[4]) for sig in atlas])
145  vcdVars = [writer.register_var(
146  sig[5], sig[0] + '[{}:{}]'.format(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('\r{} us converted'.format(str(timestamp)[:-3]), 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('[Warning] non-uniform dummy value detected at {}ns: {}'.format(
168  timestamp, sig[3] % sum(evtsize)))
169  print(values[i])
170  continue
171  if values[i] != lastvalues[i]:
172  writer.change(vcdVars[i], timestamp, values[i])
173  lastvalues = values
174  print('')
175 
176 
177 def literalVCD(triggers, atlas, writer):
178  """
179  This is slower than unpack(). Use that instead.
180  write a VCD file from clock-seperated data and hitmap
181  """
182  vcdVars = [writer.register_var(
183  'm', sig[0] + '[{}:{}]'.format(sig[1], sig[2]), 'wire', size=sig[4]) if
184  sig[0] != 'unamed' else 0 for sig in atlas]
185  event = writer.register_var('m', 'event', 'event')
186  lastvalue = None
187  iteration = 0
188  timestamp = 0
189  power = period.bit_length() - 1 # (marginal) speed optimization
190  for trg in triggers:
191  timestamp = iteration << power
192  writer.change(event, timestamp, '1')
193  for value in trg.cut(sum(evtsize)):
194  if timestamp & 1023 == 0:
195  print('\r{} us completed'.format(str(timestamp)[:-3]),)
196  sys.stdout.flush()
197  timestamp = iteration << power
198  iteration += 1
199  for i, sig in enumerate(atlas):
200  sigvalue = value[sig[3]:sig[3] + sig[4]]
201  lastsigvalue = lastvalue[sig[3]:sig[3] + sig[4]] if lastvalue else None
202  if sig[0] == 'unamed':
203  if dummycheck and sigvalue.any(1) and sigvalue.any(0):
204  with sys.stderr as fout:
205  fout.write(
206  '[Warning] non-uniform dummy value detected at {}ns: {}'.format(
207  timestamp, sig[3] % sum(evtsize)))
208  fout.write(values[i])
209  continue
210  if not lastvalue or sigvalue != lastsigvalue:
211  writer.change(vcdVars[i], timestamp, sigvalue.bin)
212  lastvalue = value
213 
214 
215 def combVCD(clocks, atlas, writer):
216  """
217  obsolete
218  """
219  comAtlas = []
220  for i in range(len(atlas)):
221  if atlas[i][0] == 'unamed' or atlas[i][0] in [x[0] for x in comAtlas]:
222  continue
223  signal = [atlas[i]]
224  for j in range(i + 1, len(atlas)):
225  if atlas[j][0] == atlas[i][0]:
226  signal.append(atlas[j])
227  signal = sorted(signal, key=itemgetter(1), reverse=True)
228  for l in signal[1:]:
229  signal[0].extend(l)
230  comAtlas.append(signal[0])
231  vars = [writer.register_var(
232  'm', sig[0] + '[{}:{}]'.format(sig[1], sig[-3]), 'wire',
233  size=sum(sig[4::5])) for sig in comAtlas]
234  for timestamp, value in enumerate(clocks):
235  for i, sig in enumerate(comAtlas):
236  writer.change(vars[i], 32 * timestamp,
237  BitArray([]).join([value[sig[n]:sig[n] + sig[n + 1]] for n in range(3, len(sig), 5)]).bin)
238 
239 
240 def writeVCD(data, atlas, fname, size, combine=False):
241  """
242  evt: 2D list of HSLB buffers
243  atlas: list of iterates
244  """
245  # combine data in different buffers into a single block of event size
246  samples = len(list(filter(None, data[0][0]))[0]) // list(filter(None, evtsize))[0]
247  NullArray = BitArray([])
248  # clocks = BitArray([])
249  clocks = []
250  for evt in data:
251  for entry in evt:
252  clocks.append(BitArray([]).join(
253  itertools.chain.from_iterable(zip(
254  *[entry[buf].cut(size[buf])
255  if size[buf] > 0 else [NullArray] * samples
256  for buf in range(4)]))))
257  with open(fname, 'w') as fout:
258  with VCDWriter(fout, timescale='1 ns', date='today') as writer:
259  if combine:
260  # combVCD(clocks, atlas, writer)
261  raise Exception('Combine has not been updated. Use literal instead.')
262  else:
263  # literalVCD(clocks, atlas, writer)
264  unpack(clocks, atlas, writer)
265 
266 
267 if __name__ == "__main__":
268  import argparse
269  parser = argparse.ArgumentParser()
270  parser.add_argument("-i", "--pickle", help="input pickle file")
271  parser.add_argument("-s", "--signal", help="input signal file")
272  parser.add_argument("-o", "--output", help="output VCD file")
273  parser.add_argument("-nc", "--nocheck", help="disable dummy variable check",
274  action="store_true")
275  args = parser.parse_args()
276  if args.signal:
277  with open(args.signal) as fin:
278  evtsize = [int(width) for width in fin.readline().split()]
279  print('interpreting B2L data with dimension ' + str(evtsize))
280  atlas = makeAtlas(fin.read(), evtsize)
281  else:
282  atlas = makeAtlas(signalsall, evtsize)
283 
284  pica = args.pickle
285  rfp = open(pica, 'rb')
286  data = pickle.load(rfp)
287  rfp.close()
288 
289  if args.nocheck:
290  dummycheck = False
291  fname = args.output if args.output else pica[:pica.rfind('.')] + '.vcd'
292  writeVCD(data, atlas, fname, evtsize)
unpack
Definition: unpack.py:1
Belle2::filter
std::map< ExpRun, std::pair< double, double > > filter(const std::map< ExpRun, std::pair< double, double >> &runs, double cut, std::map< ExpRun, std::pair< double, double >> &runsRemoved)
filter events to remove runs shorter than cut, it stores removed runs in runsRemoved
Definition: Splitter.cc:43