+ # Terminate a previously seen item of a word first. Emit the
+ # word's annotation when the last item's end was seen.
+ if self.word_items:
+ ss, _, data = self.word_items[-1]
+ es = now
+ self.word_items[-1] = (ss, es, data)
+ if len(self.word_items) == wordsize:
+ self.flush_word(bus_width)
+
+ # Start tracking the currently seen item (yet unknown end time).
+ if item is not None:
+ pend = (now, None, item)
+ self.word_items.append(pend)
+
+ def handle_bits(self, now, item, bus_width):
+
+ # Optionally flush a previously started item.
+ if self.pend_item:
+ ss, _, data = self.pend_item
+ self.pend_item = None
+ es = now
+ txts = [self.fmt_item.format(data)]
+ self.putg(ss, es, Ann.ITEM, txts)
+ self.putpy(ss, es, 'ITEM', data)
+ # self.putpy(ss, es, 'ITEM', (data, bus_width))
+
+ # Optionally queue the currently seen item.
+ if item is not None:
+ self.pend_item = (now, None, item)
+
+ # Pass the current item to the word accumulation logic.
+ self.queue_word(now, item, bus_width)
+
+ def decode(self):
+ # Determine which (optional) channels have input data. Insist in
+ # a non-empty input data set. Cope with sparse connection maps.
+ # Store enough state to later "compress" sampled input data.
+ data_indices = [
+ idx if self.has_channel(idx) else None
+ for idx in range(Pin.DATA_0, Pin.DATA_N)
+ ]
+ has_data = [idx for idx in data_indices if idx is not None]
+ if not has_data:
+ raise ChannelError('Need at least one data channel.')
+ max_connected = max(has_data)
+
+ # Pre-determine which input data to strip off, the width of
+ # individual items and multiplexed words, as well as format
+ # strings here. This simplifies call sites which run in tight
+ # loops later.
+ upper_data_bound = max_connected + 1
+ num_item_bits = upper_data_bound - Pin.DATA_0
+ num_word_items = self.options['wordsize']
+ num_word_bits = num_item_bits * num_word_items
+ num_digits = (num_item_bits + 4 - 1) // 4
+ self.fmt_item = "{{:0{}x}}".format(num_digits)
+ num_digits = (num_word_bits + 4 - 1) // 4
+ self.fmt_word = "{{:0{}x}}".format(num_digits)
+
+ # Determine .wait() conditions, depending on the presence of a
+ # clock signal. Either inspect samples on the configured edge of
+ # the clock, or inspect samples upon ANY edge of ANY of the pins
+ # which provide input data.
+ conds = []
+ cond_idx_clock = None
+ cond_idx_data_0 = None
+ cond_idx_data_N = None
+ cond_idx_reset = None
+ has_clock = self.has_channel(Pin.CLOCK)
+ if has_clock:
+ cond_idx_clock = len(conds)
+ edge = {
+ 'rising': 'r',
+ 'falling': 'f',
+ 'either': 'e',
+ }.get(self.options['clock_edge'])
+ conds.append({Pin.CLOCK: edge})
+ else:
+ cond_idx_data_0 = len(conds)
+ conds.extend([{idx: 'e'} for idx in has_data])
+ cond_idx_data_N = len(conds)
+ has_reset = self.has_channel(Pin.RESET)
+ if has_reset:
+ cond_idx_reset = len(conds)
+ conds.append({Pin.RESET: 'e'})
+ reset_active = {
+ 'low-active': 0,
+ 'high-active': 1,
+ }.get(self.options['reset_polarity'])
+
+ # Keep processing the input stream. Assume "always zero" for
+ # not-connected input lines. Pass data bits (all inputs except
+ # clock and reset) to the handle_bits() method. Handle reset
+ # edges first and data changes then, within the same iteration.
+ # This results in robust operation for low-oversampled input.
+ in_reset = False
+ while True:
+ pins = self.wait(conds)
+ clock_edge = cond_idx_clock is not None and self.matched[cond_idx_clock]
+ data_edge = cond_idx_data_0 is not None and [idx for idx in range(cond_idx_data_0, cond_idx_data_N) if self.matched[idx]]
+ reset_edge = cond_idx_reset is not None and self.matched[cond_idx_reset]
+
+ if reset_edge:
+ in_reset = pins[Pin.RESET] == reset_active
+ if in_reset:
+ self.handle_bits(self.samplenum, None, num_item_bits)
+ self.flush_word(num_item_bits)
+ if in_reset: