-def equal_leading_zero_count_reference(a, b, width):
- """checks if `clz(a) == clz(b)`.
- Reference code for algorithm used in `EqualLeadingZeroCount`.
- """
- assert isinstance(width, int) and 0 <= width
- assert isinstance(a, int) and 0 <= a < (1 << width)
- assert isinstance(b, int) and 0 <= b < (1 << width)
- eq = True # both have no leading zeros so far...
- for i in range(width):
- a_bit = (a >> i) & 1
- b_bit = (b >> i) & 1
- # `both_ones` is set if both have no leading zeros so far
- both_ones = a_bit & b_bit
- # `different` is set if there are a different number of leading
- # zeros so far
- different = a_bit != b_bit
- if both_ones:
- eq = True
- elif different:
- eq = False
- else:
- pass # propagate from lower bits
- return eq
-
-
-class EqualLeadingZeroCount(Elaboratable):
- """checks if `clz(a) == clz(b)`.
-
- Properties:
- width: int
- the width in bits of `a` and `b`.
- a: Signal of width `width`
- input
- b: Signal of width `width`
- input
- out: Signal of width `1`
- output, set if the number of leading zeros in `a` is the same as in
- `b`.
- """
-
- def __init__(self, width):
- assert isinstance(width, int)
- self.width = width
- self.a = Signal(width)
- self.b = Signal(width)
- self.out = Signal()
-
- def elaborate(self, platform):
- # the operation is converted into calculation of the carry-out of a
- # binary addition, allowing FPGAs to re-use their specialized
- # carry-propagation logic. This should be simplified by yosys to
- # remove the extraneous xor gates from addition when targeting
- # FPGAs/ASICs, so no efficiency is lost.
- #
- # see `equal_leading_zero_count_reference` for a Python version of
- # the algorithm, but without conversion to carry-propagation.
- # note that it's possible to do all the bits at once: a for-loop
- # (unlike in the reference-code) is not necessary
-
- m = Module()
- both_ones = Signal(self.width)
- different = Signal(self.width)
-
- # build `both_ones` and `different` such that:
- # for every bit index `i`:
- # * if `both_ones[i]` is set, then both addends bits at index `i` are
- # set in order to set the carry bit out, since `cin + 1 + 1` always
- # has a carry out.
- # * if `different[i]` is set, then both addends bits at index `i` are
- # zeros in order to clear the carry bit out, since `cin + 0 + 0`
- # never has a carry out.
- # * otherwise exactly one of the addends bits at index `i` is set and
- # the other is clear in order to propagate the carry bit from
- # less significant bits, since `cin + 1 + 0` has a carry out that is
- # equal to `cin`.
-
- # `both_ones` is set if both have no leading zeros so far
- m.d.comb += both_ones.eq(self.a & self.b)
- # `different` is set if there are a different number of leading
- # zeros so far
- m.d.comb += different.eq(self.a ^ self.b)
-
- # now [ab]use add: the last bit [carry-out] is the result
- csum = Signal(self.width + 1)
- carry_in = 1 # both have no leading zeros so far, so set carry in
- m.d.comb += csum.eq(both_ones + (~different) + carry_in)
- m.d.comb += self.out.eq(csum[self.width]) # out is carry-out
-
- return m
-
-