+ # internal signals exposed for unit tests, should be ignored by
+ # external users. The signals are created in the constructor because
+ # that's where all class member variables should *always* be created.
+ # If we were to create the members in elaborate() instead, it would
+ # just make the class very confusing to use.
+ #
+ # `_intermediates[step_count]`` is the value after `step_count` steps
+ # of muxing. e.g. (for `msb_first == False`) `_intermediates[4]` is the
+ # result of 4 steps of muxing, being the value `grev(inp,0b00wxyz)`.
+ self._intermediates = [self.__inter(i) for i in range(log2_width + 1)]
+
+ def _get_cs_bit_index(self, step_index):
+ """get the index of the bit of `chunk_sizes` that this step should mux
+ based off of."""
+ assert 0 <= step_index < self.log2_width
+ if self.msb_first:
+ # reverse so we start from the MSB, producing intermediate values
+ # like, for `step_index == 4`, `0buvwx00` rather than `0b00wxyz`
+ return self.log2_width - step_index - 1
+ return step_index
+
+ def __inter(self, step_count):
+ """make a signal with a name like `grev(inp,0b000xyz)` to match the
+ diagram in the module-level docs."""
+ # make the list of bits in LSB to MSB order
+ chunk_sizes_bits = ['0'] * self.log2_width
+ # for all steps already completed
+ for step_index in range(step_count):
+ bit_num = self._get_cs_bit_index(step_index)
+ ch = string.ascii_lowercase[-1 - bit_num] # count from z to a
+ chunk_sizes_bits[bit_num] = ch
+ # reverse cuz text is MSB first
+ chunk_sizes_val = '0b' + ''.join(reversed(chunk_sizes_bits))
+ # name works according to Verilog's rules for escaped identifiers cuz
+ # it has no spaces
+ name = f"grev(inp,{chunk_sizes_val})"
+ return Signal(self.width, name=name)
+
+ def __get_permutation(self, step_index):
+ """get the bit permutation for the current step. the returned value is
+ a list[int] where `retval[i] == j` means that this step's input bit `i`
+ goes to this step's output bit `j`."""
+ # we can extract just the latest bit for this step, since the previous
+ # step effectively has it's value's grev arg as `0b000xyz`, and this
+ # step has it's value's grev arg as `0b00wxyz`, so we only need to
+ # compute `grev(prev_step_output,0b00w000)` to get
+ # `grev(inp,0b00wxyz)`. `cur_chunk_sizes` is the `0b00w000`.
+ cur_chunk_sizes = 1 << self._get_cs_bit_index(step_index)
+ # compute bit permutation for `grev(...,0b00w000)`.
+ return [i ^ cur_chunk_sizes for i in range(self.width)]
+
+ def _sigs_and_expected(self, inp, chunk_sizes):
+ """the intermediate signals and the expected values, based off of the
+ passed-in `inp` and `chunk_sizes`."""
+ # we accumulate a mask of which chunk_sizes bits we have accounted for
+ # so far
+ chunk_sizes_mask = 0
+ for step_count, intermediate in enumerate(self._intermediates):
+ # mask out chunk_sizes to get the value
+ cur_chunk_sizes = chunk_sizes & chunk_sizes_mask
+ expected = grev(inp, cur_chunk_sizes, self.log2_width)
+ yield (intermediate, expected)
+ # if step_count is in-range for being a valid step_index
+ if step_count < self.log2_width:
+ # add current step's bit to the mask
+ chunk_sizes_mask |= 1 << self._get_cs_bit_index(step_count)
+ assert chunk_sizes_mask == 2 ** self.log2_width - 1, \
+ "should have got all the bits in chunk_sizes"
+