Patrick W.

Tags

  • fpga
  • maths

While searching for a way to take logs on an FPGA (without resorting to the sledgehammer of CORDIC) I stumbled upon Micheal Dunn’s Single-cycle logarithms & antilogs article in EDN. His method is a clever extention of the mathematician’s approximation: to take the log (base 10), just count the digits. In binary we can get the log (base 2) by “counting the digits” with a priority encoder—which outputs the position of the highest nonzero bit.

This bit-counting is pretty cheap, and gives us the integer part of the log. Next is is the clever bit: we can get the fractional part by left-aligning the input with a barrel shifter (by left shifting the input by the binary not of the integer part) then feeding that into a LUT (???). How on earth that works (and besides… what goes in the LUT?) is what I want to analyse here, as it falls quite neatly out of some algebra.

(TODO: add diagram here)

To work out the behaviour mathematically, first consider the priority encoder output Po, which is the floor of the log of the input. The floor a is simply a{a} where {a} denotes the fractional part of a, thus for input value x:

Po=log2x=log2x{log2x}

Po feeds straight to the output as the integer part, but before feeding it into the barrel shifter we not its bits. Why we have to do this will be apparent soon—we need to flip the signs of the terms in Po to get some cancellation. For now, note that the binary not b of the bits of a number b means for inputs counting up …000, …001, …010 we’ll now be counting down …111, …110, …101. That is to say b=(2N1)b, where N is the bit-width of b (and thus 2N1 its maximum possible value).

We can now write the equation for the output of the barrel shifter Bo:

Bo=x<<Po=x<<(2N1Po)

As left-shifting is exponentiation, we have:

Bo=x2(2N1Po)=x2(2N1)2Po

We can substitute in Po and nicely cancel out the x:

Bo=x2(2N1)2Po=x2(2N1)2(log2x{log2x})=2(2N1)2{log2x}

This is why we had to bitwise not Po going into the barrel shifter, the not lead to the subtraction Po in the equation for Bo, thus the log2x term in Po cancels the x from the input to the barrel shifter. Now just take the log of both sides, and rearrange for {log2x}:

log2Bo=(2N1)+{log2x}

This gives us the equation for the fractional part {log2x} in terms of the barrel shifter output Bo, which is the equation that the LUT implements:

{log2x}=log2Bo(2N1)

At first glance it looks like we’re right back where we started, as we have to take the log of Bo, but consider the fact that—by definition—{log2x} must be between zero and one, being the fractional part of log2x. Thus, the value of the right hand side must also lie between zero and one. Let’s rearrange it to get:

{log2x}=log2Bo2(2N1)=log2(Bo>>(2N1))

Recall that 2N1 is the maximum possible value of Po, that is to say: it’s the bit-width of the input x.

// TODO

As a sanity check, substitute the definition of Bo and then Po back in:

{log2x}=log2(Bo>>(2N1))=log2((x<<(2N1Po))>>(2N1)) {log2x}=log2(x>>Po)=log2(x2Po) {log2x}=log2(x)Po=log2(x)log2x

Which is exactly the definition of {log2x}, the fractional part of the log.