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