Top Banner
EE Times-Asia | eetasia.com Copyright © 2013 eMedia Asia Ltd. Page 1 of 2 Performing math operations in FPGAs There are several tricks that can be used to do mathematical operations efficiently in FPGA hardware. By Tom Burke Senior Systems Engineer I tried doing "real" (that is, non-integer) math on a Spartan 3 FPGA for a recent project. FPGAs, by their nature, do integer math. That is, there's no floating-point unit (or even fixed-point) in there unless you "roll your own" (I think many of the newer devices have dedicated math functions in them). This led me (temporarily) down the path of trying to convert the "real" numbers I was working with (that are fairly small) into integers (by multiplication by 10 -- basically multiplying by powers) to perform the function. Why would I want to do this? Well, I was trying to get over the pains of (large) combinational multipliers and cumbersome dividers. Of course, multiplication is just repeated addition; but it appears that my synthesis tools prefer to generate them from combinational logic. This makes for faster speed at the sacrifice of real estate (or so I suppose). The synthesis tools, however, do not seem to do this for division. This leaves us with a potential problem, then. If I roll my own divider, it will likely do successive additions/subtractions to compute a result. This means that I would have a stand-alone unit that would need to incorporate "ready" signalling to the next higher assembly. That is, I would have to hand it the numbers to divide and then wait for the module to signal that it had completed the operation before I could continue on with my process. But, if we can contrive our math such that we are always using powers of two, then we can use shifts, right? That is, a left shift of one bit is equivalent to a multiply by two, while a right shift of one bit is equivalent to a divide by two: This is handy if we can keep ourselves in the binary world, but sometimes it's nice to work in the more familiar decimal world. To this end, I thought I'd share a couple of tricks I've learned over the years. The first trick is a fast way to multiply by 10. Even these days, this is a faster way to perform a "multiply by 10" function than what your CPU will normally do when it sees X * 10. So, are you ready to do some math? Yep -- if you perform a left-shift of one bit and add it to a left shift of three bits (2 3 = 8), then you've got a multiply by 10. In an FPGA, this is easily done in a single clock cycle... (on an older Intel CPU, it takes three clock cycles, while a normal multiply takes five or more). Now, dividing is not so easy, but there is a way to do it. Allegedly, this is what the Microsoft compilers do when they see a ÷10, as it's much faster than doing an actual division (which is slow, slow, slow). What I'm about to show you is all "smoke and mirrors" to me as I've never seen the derivation, but it surely seems to work. Are you ready to scratch your head a lot and then (on your own) try a bunch of examples to prove it really works? Here's what to do... whatever your word length, fill a register with 19...9Ah. That is, if you have a 16bit word, load a register with 199Ah (199A in hexadecimal). Similarly, if you have a 32bit system, then fill a register with 1999999Ah. Now multiply this by your number (1 clock in the FPGA) to generate a double-word result (that is, a 32bit result on a 16bit system or a 64bit result on a 32bit system). If you throw away the lower half of the result, you'll find that the upper half is identical to dividing the original number by 10. Here's a pictorial depiction of the process:
17

Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

Mar 06, 2018

Download

Documents

truongkhanh
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2013 eMedia Asia Ltd. Page 1 of 2

Performing math operations in FPGAs

There are several tricks that can be used to do mathematical operations efficiently in

FPGA hardware.

By Tom Burke

Senior Systems Engineer

I tried doing "real" (that is, non-integer) math on a Spartan 3 FPGA for a recent project. FPGAs, by their nature, do

integer math. That is, there's no floating-point unit (or even fixed-point) in there unless you "roll your own" (I think many of the newer devices have dedicated math functions in them).

This led me (temporarily) down the path of trying to convert the "real" numbers I was working with (that are fairly

small) into integers (by multiplication by 10 -- basically multiplying by powers) to perform the function. Why would

I want to do this? Well, I was trying to get over the pains of (large) combinational multipliers and cumbersome

dividers. Of course, multiplication is just repeated addition; but it appears that my synthesis tools prefer to generate

them from combinational logic. This makes for faster speed at the sacrifice of real estate (or so I suppose).

The synthesis tools, however, do not seem to do this for division. This leaves us with a potential problem, then. If I roll my own divider, it will likely do successive additions/subtractions to compute a result. This means that I would

have a stand-alone unit that would need to incorporate "ready" signalling to the next higher assembly. That is, I

would have to hand it the numbers to divide and then wait for the module to signal that it had completed the

operation before I could continue on with my process.

But, if we can contrive our math such that we are always using powers of two, then we can use shifts, right? That is,

a left shift of one bit is equivalent to a multiply by two, while a right shift of one bit is equivalent to a divide by two:

This is handy if we can keep ourselves in the binary world, but sometimes it's nice to work in the more familiar

decimal world. To this end, I thought I'd share a couple of tricks I've learned over the years.

The first trick is a fast way to multiply by 10. Even these days, this is a faster way to perform a "multiply by 10"

function than what your CPU will normally do when it sees X * 10. So, are you ready to do some math?

Yep -- if you perform a left-shift of one bit and add it to a left shift of three bits (23 = 8), then you've got a multiply by

10. In an FPGA, this is easily done in a single clock cycle... (on an older Intel CPU, it takes three clock cycles, while a

normal multiply takes five or more).

Now, dividing is not so easy, but there is a way to do it. Allegedly, this is what the Microsoft compilers do when they

see a ÷10, as it's much faster than doing an actual division (which is slow, slow, slow). What I'm about to show you

is all "smoke and mirrors" to me as I've never seen the derivation, but it surely seems to work. Are you ready to scratch your head a lot and then (on your own) try a bunch of examples to prove it really works?

Here's what to do... whatever your word length, fill a register with 19...9Ah. That is, if you have a 16bit word, load a

register with 199Ah (199A in hexadecimal). Similarly, if you have a 32bit system, then fill a register with

1999999Ah. Now multiply this by your number (1 clock in the FPGA) to generate a double-word result (that is, a

32bit result on a 16bit system or a 64bit result on a 32bit system). If you throw away the lower half of the result,

you'll find that the upper half is identical to dividing the original number by 10. Here's a pictorial depiction of the

process:

Page 2: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2013 eMedia Asia Ltd. Page 2 of 2

Simple, huh? I'm sure most of you already know this, but there's always a few of us that could use an occasional

reminder. I mentioned that I had started down the multiply-by-10 and divide-by-10 path to try to get around the need for "real" number representations in my project. Well, I was never quite able to "tease" that out -- it got too

complex too fast. I'm sure one of you folks that's a lot smarter than myself (which is pretty much any of you, I'm

sure) could have figured it out, but I'm me, and that's where we'll leave it for now (EE Times editor, Max Maxfield,

hates it when I get long-winded, which is pretty easy for me to do).

Next time I'll talk about the implementation I settled on for my project, along with some of the reasons I chose the

options I did. �

About the author

Tom Burke is Senior Systems Engineer.

Page 3: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 1 of 4

Performing math operations in FPGAs (Part

2)

Binary coded decimal is not used as much as it once was, but we can still find it

around—mostly in applications where the data will be directly driving a display.

By Tom Burke

Senior Systems Engineer

In my previous article, I discussed some simple methods to multiply and divide by 10 in any general-purpose

device, not just in an FPGA. Some readers asked why in the heck I would even consider doing such a thing (as

opposed to sticking with binary representations). Well, the answer is along the lines of "We are base-10 creatures

living in a base-10 world." Much of what we do is related to the decimal system, so we often find the need to

multiply and divide by 10. Let's figure out some ways to do it quickly and efficiently. Sometimes this can lead to

things that work better in the base-2 world of numerical machines.

I think I mentioned this last time, but my quick investigation into such frivolity didn't work out for me. I didn't find an easier way to do the math. It was time for me to get back in the box and conform to binary (drat).

What to do? There are plenty of ways to represent real numbers in a binary system. I would guess the three most

common are probably binary coded decimal (BCD), floating point, and fixed point. Each has its strengths and

weaknesses, depending on your end game. As such, I guess this issue will feature a brief discussion of these three

representations and perhaps will touch on their strengths, weaknesses, and implementations.

Let's start with BCD. It's not used as much as it once was, but we can still find it hither and yon -- mostly in applications where the data will be directly driving a display of some sort (e.g., a seven-segment display). And this,

of course, is the beauty of BCD. The individual digits not only represent the decimal system with which we are

naturally comfortable, but they can also be treated as separate circuits for mathematical operations. There's

another positive ramification to this representation: We can represent any number exactly if we are willing to

allocate the bits to do so. For instance, 0.2 in BCD is 0.2, but in binary, it might be 00110011... (in, say, a fractional-

only fixed-point representation).

Let's take a quick look at just what BCD is. There are, in fact, multiple formats for this representation, but (ignoring compressed and uncompressed) the most common usage is shown in the following table.

Now that we know what it is, how do we use it? In an FPGA, I would imagine that the most likely use might be in

output and input encoding to some external (human) user. With that thought in mind, let's talk about how we might

go about interfacing to the outside world.

Let's consider the case where we want to do all our manipulations in binary. In this case, we will want to convert

our BCD encoded data into binary. This can be achieved as illustrated below.

We start off with a binary value of zero, and then we add each BCD digit multiplied by its decimal power. Of course,

those multipliers might be pretty big, but if we get clever, maybe we can use the x10 schemes we discussed in my

Page 4: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 2 of 4

previous blog to our advantage and replace these complex multiplications in a pipelined configuration of shifts and

adds. I'll leave it you to ponder on that for a while and figure out how to do it for yourself.

Once we're done with our binary machinations/manipulations, we might want to display the result to a seven-

segment display. Unfortunately, binary doesn't map directly to BCD, does it?

To translate from binary to BCD, we can employ the shift-and-add-3 algorithm:

1. Left-shift the (n-bit) binary number one bit.

2. If n shifts have taken place, the number has been fully expanded, so exit the algorithm. 3. If the binary value of any of the BCD columns is greater than or equal to 5, add 3.

4. Return to (1).

As an example, let's use the nine-bit number 100101110 (302 decimal) and run it through our algorithm.

In some cases, maybe we shouldn't even bother converting back and forth between binary and BCD. Doing math

with BCD is fairly straightforward. As an added benefit, we can treat each digit as a (mostly) separate circuit. Let's

talk about addition, which is performed as follows.

1. Align the decimal points (if necessary).

2. Starting from the right (the one's digit), add the two numbers.

3. If the result is greater than 9 OR the result generates a carry, then add 6.

4. Propagate the carry to the next digit.

5. If all digits have been added, we're done.

6. Go to (2).

As an example, let's use our algorithm to add 93 and 79. (It does not matter which order you perform the adds, so long as you check between each for the out-of-bounds condition.)

Page 5: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 3 of 4

This leaves BCD subtraction, which is a little more interesting. Of course, multiplication is just successive addition,

so we can get there from here. In fact, we can get to division from here, too, but we still need subtraction. When

working in binary, we often use the one's complement or two's complement formats to represent negative

numbers. This allows us to perform subtractions by simply adding the two numbers. (Subtracting is just taking the

negative of the number to be subtracted and then adding them, right?) This is true in decimal, too, only we use

either nine's or ten's complement representations. Wait, what are nine's and ten's complements? To be honest, I don't remember learning this in school. It works like

this -- consider the following subtraction problem.

Another way to present this problem would be as follows.

And this is the same as performing the following actions.

Let's take this step by step: First, let's perform the following subtraction.

Simply put, 9,722 is the nine's complement of -277. This does not solve our subtraction problem in and of itself,

since we still need to perform a subtraction to obtain the nine's complement. Fortunately, there will never be a

borrow from a next-higher-power column. (I'll leave it to you to figure it out.) We can use a lookup table or

something similar to derive each individual digit.

Page 6: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 4 of 4

The nine's complement of -277 is 9,722 (the same as performing the subtraction). Now we add 1 to generate the

tens's complement.

Next, we add the ten's complement of -277 to 4,337.

We still need to subtract 10,000 from our result, but we can easily achieve this by throwing away (ignoring) the

most significant digit. Our result is 4,060. If the leftmost digit is 5 or greater, we know the number is negative. To convert to the proper BCD representation (for display), we just use our lookup table to get the nine's complement.

Reversing the numbers, we get the following (with a negative sign, which we might need to monitor separately,

depending on our implementation).

Because the first digit is greater than or equal to 5, we know this is a negative number. Taking the nine's

complement of 5,940 gives 4,060, so our answer is -4,060. Problem solved.

I've expended a lot of hot air explaining something you probably already knew. At least we now have the tools to

perform decimal math in BCD using addition, subtraction, multiplication, and division. I'll leave it to you to

implement this in hardware. Granted, this is pretty inefficient in terms of hardware and memory (something like

40% more hardware), and it is considerably slower and more klunky. However, we gain exact representation of our

digits, and there is no issue with rounding/truncation. (We'll talk about this in another installment, I think.) This could be useful, but your mileage may vary. As always, you need to take a close look at your problem and figure out

the best solution.

In my next column, I think I'll talk about some simple floating-point stuff. Until then, if you have any questions,

please post them below as comments. �

About the author

Tom Burke is Senior Systems Engineer.

Page 7: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 1 of 3

Performing math operations in FPGAs (Part

3)

This instalment focuses on floating-point representations of numbers and how to do

some math with them.

By Tom Burke

Senior Systems Engineer

Here's a review of the previous articles in this series. In Part 1, I muttered some inanities about multiplication and

division by 10. In Part 2, I rambled on about doing math in BCD. Now, it seems it's time to mutter something about

floating-point representations of numbers and how to do some math with them. I considered using floating-point

representations for this mysterious project that I've been alluding to (I'll get to that, one of these days, maybe), so I

took a quick look at how to implement them.

Now, of course, there are plenty of ways one could represent a floating-point number. You can do it your way, I can

do it my way, or we can all agree to follow a standard such as the IEEE 754 2008 standard, for example. Of course, I'm not the first person here to cover the topic of floating-point representations; in fact, Mr. Kjodavix described this

way back in 2006. Because of Mr. Kjodavix's article, I wondered whether I should even bother expounding on

floating-point concepts. However, we all speak a little differently and we all learn a little differently, so maybe my

take on this will make someone else's grasp a little better (I do recommend reading Mr. Kjodavix's article, though).

So what are floating-point numbers? Well, let's start with the fact that, due to the way in which we build our

computers using two-state logic (let's not worry about experiments with tertiary, or three-state, logic), we have to store numbers using some form of binary representation. It's relatively easy to use binary values to represent

integers, but they don't lent themselves to directly storing real numbers; that is, numbers that include fractional

values with digits after the decimal point. In other words, it's relatively easy to use binary to represent a value like

3, but it's less easy to represent a value like 3.141592. Similarly, it's relatively easy to create logic functions to

implement mathematical operations on integer values, but it's less easy to work with real numbers.

Of course, we can store numbers in BCD (I talked about this in Part 2), or we could use fixed-point representations (I

will talk about this next time), but what do we actually mean by floating-point? Well, it's a lot like the "scientific notation" we learned at high school (e.g. 31.41592x10 -1), but it's stored and manipulated using binary

representations.

So, how we might perform the mighty feat of representing a real number in binary? If we would just assume a

binimal point (the binimal point is the same as the decimal point in base 10, only it's the binary equivalent in base

2) at some fixed point in the middle, then we'd have a fixed-point representation as illustrated below.

I won't yammer on about this right now (that's for next time); suffice it to say that we would need a lot of bits to

represent either a really big number or a really small one. Floating-point solves this problem by breaking the

number up into three pieces: the sign, the mantissa (a.k.a. significand or coefficient), and the exponent (a.k.a.

characteristic or scale). This gives us a fairly large dynamic range. The generic form is as follows:

Page 8: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 2 of 3

Where:

n = the number being represented

± = the sign of the number

x = the mantissa of the number

b = the number system base (10 in decimal; 2 in binary)

y = the exponent (power) of the number (which can itself be positive, or negative)

Easy, right? Well, maybe not so -- there are some tricks involved, as well as a variety of benefits and drawbacks. So,

how do we represent floating-point in our device? Well, there's plenty of different ways to do this, there's your way,

there's my way, and there's some other guy's way.

For example, the exponent is usually an integer. We could extend this by allowing the exponent to have a fractional representation if we really wanted. In general, though, I don't know why we'd want to do that, as the result would

just be another fractional number that we could easily represent (unless the exponent and the mantissa were both

negative, in which case we'd have a complex number, and there are easier ways to represent those).

For the purposes of this column we will focus on the IEEE 754 2008 floating-point standard (hereinafter referred to

as "754"). 754 defines a couple of implementations, primarily based on the width of their mantissa. These are Half,

Single, Double, Double Extended, and Quad Precision. The binary representations of these would be as follows:

754 also includes some special formatting for certain values, such as NaN (not a number), infinity, and some others.

I'll leave it to you to research those. For clarity, I'll stick to the half-precision (16bit) format in this article. Except for

the range of possible values and biases (which I'll blather on about in a bit), things work the same for each type.

First, there's the sign bit. If our number is negative, then the sign bit will be a "1," otherwise it will be a zero

(negative zero is possible to keep divide by zeroes "honest"). Easy, right?

Next is the exponent. Here, there is a trick; as the exponent does not have a sign bit, and we (should) all know that exponents can be negative. The trick is that the exponent has a bias which must be subtracted in order to find its

true value. The bias can be computed as follows:

Or, equivalently:

Where:

b = the bias

n = the number of bits in the exponent

More simply, the biases are shown in the table below:

Page 9: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 3 of 3

This means that in our half-precision number, our exponent can have the range [-15 to 16]. That's a lot of zeroes!

But this introduces one of the drawbacks of floating point numbers, and that is binimal point alignment. So, imagine

that we're adding two numbers with different exponents -- we first need to shift one of the numbers (or both) until

their binimal points are aligned. We also need to keep track of the result of the addition and update the exponent if

there was a carry out (overflow).

The final part of our floating point number is the mantissa. In our half-precision implementation there are 11 bits of

information. "Wait," you might say, "there are actually only 10 bits!" This is true, but the trick is that the 11th bit

(the most-significant bit) is implied. Basically, you keep shifting the mantissa left (and modifying your exponent

accordingly) until you find the first/last '1' (depending on the sign of the exponent), at which point you "throw that

'1' away." Here's an example -- let's store the number 0.02 as follows:

So, that's how we store our number into the floating-point format. Even though the floating-point format has the

advantages of high dynamic range in a fairly compact space, we can also see that there are some disadvantages as follows:

1. Floating-point (like all binary representations) does not map well into decimal (that is, the precision limits

the translation).

2. You may need to apply the bias prior to an operation (not necessary for multiplication or division, as the

exponents add/subtract).

3. To add two numbers, you must first "unroll" the exponents to align their binimal points. 4. After a math operation, you must "reroll" the exponents.

5. The act of "unrolling" the exponents can lead to a loss of precision if your registers are too narrow.

There is also a disadvantage in that you may need to "intelligently" decide which number to unroll for a given

operation -- that is, there needs to be a decision made about which value is more significant so you don't lose (or

gain) significance during the unrolling operations. As an example, consider 3.24 + 0.02001; which of these should

lose bits if it proves necessary to do so? The answer is 0.02001, as the result cannot be more "precise" than any of

the inputs.

Another drawback that should be obvious at this point is truncation. 754 defines a couple of different ways to

perform rounding, but I'm betting that in many cases we might not want to waste the extra hardware to do that.

Unfortunately, truncation (unlike rounding) always "pushes" our results towards zero (smaller magnitude). This

may not seem like a big deal for a single operation, but truncation errors tend to "stack up" over multiple operations

and can badly skew results. This is a big enough deal that IBM used to add guard bits in their machinery back in the

bad old days.

On the plus side, the floating-point format provides a relatively compact way to store data, and you can always "roll your own" format for customised needs. Having said this, when talking to other computing machinery, the IEEE

standard is widely used, so sticking with this might make life easier for a lot of other people.

Of course, there are libraries available that claim to do all the hard stuff for you, but since I don't have any

experience with those, I won't comment on the veracity of their claims.

Once again, as I stated in the last couple articles, it's your job to understand your needs and to choose the method

that is best for your application. Next time, we'll take a look at fixed-point representations; in the meantime, please

post any questions or comments below. �

About the author

Tom Burke is Senior Systems Engineer.

Page 10: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 1 of 4

Performing math operations in FPGAs (Part

4)

This instalment focuses on fixed-point representations of numbers.

By Tom Burke

Senior Systems Engineer

In my previous article, I rambled on about floating-point representations. This instalment will be the last of these

rantings about things you probably already know (at least, related to the representation of real numbers). In my

silly little project, I looked into BCD, floating-point, and fixed-point. What I eventually settled on was fixed-point.

Like the other implementations we've discussed, there are plenty of ways to roll your own, but I settled on what I believe is generally called the Q,N Format. Or maybe I did roll my own. Regardless, I'll talk about the version I am

using in my little project, and we'll go with that. It seems to me that there are as many ways to implement this as

there are people trying to implement it, so be forewarned. This is my implementation (and I'm a certified

professional idiot).

I will start by giving some credit to Sam Skalicky and his OpenCores.Org project, though I have modified his material

extensively. I will also point you to this paper. In addition to glossing over some things, I'm also a know-nothing

bozo (in case you haven't already figured that out). I think that, right this second, we'd all be better served if I rehashed some of the things I said in my previous blog.

This time, I'll try to relate things to fixed-point math and how I am applying it here. Sorry, this may go long, so skip

down about three paragraphs if you already know about fixed-point and your eyes start to glaze over.

Let's start by looking at how we represent numbers in our base-10 system, which originated because most of us

have 10 fingers.

This shouldn't be anything new to you. We all learned this in elementary school. It's elementary, Watson. (Sorry, I

couldn't resist.) If I think of this as a computer register with 10 possible states (0-9), then the above could represent

a register. If the maximum number of digits we have on either side of the decimal point is three, then the biggest

number I can represent is ±999.999, assuming I keep that far left bucket as a sign bit.

The same is true of a base-2 binary system, only the powers are of two, rather than 10.

Page 11: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 2 of 4

This is just like with the base-10 system, only now we're dealing with powers of 2. So, this is how we translate a

binary number into our decimal system. If we have the same limits imposed as before -- three digits on either side of

the binimal point -- then we can represent the range of ±7.65, provided I did my math correctly. And this is the basis

for how we perform fixed-point math, except for the fact that we get to choose where the binimal point is; never mind about two's complement format, etc.

Practically speaking, we want to keep the maximum number of fractional bits (to get better resolution) without

sacrificing errors on our integer bits. We want as much dynamic range as we need, but no more. Fractional errors

add up over time, but integer errors skew the results immediately.

First, let's talk about addition. Adding fixed-point binary numbers is easy. We just perform a normal add (with a

caveat) as if it were all an integer number. If one of the numbers is negative, we take the two's complement (invert

and add 1) to get the negative representation. Do you remember ten's complement from the last installment? Yep,

any base has its own complement series, and they all work basically the same way.

Our caveat is that the number of bits required to add two numbers, the biggest of which has N bits, is N+1. This

means that, in order to add a four-bit number to another number of four or fewer bits, I will need a five-bit field in

which to store the result. Here's an example.

This is not a formal proof, mind you, but it does demonstrate that we may need to be careful later. As long as we're sure we don't overflow, this is awesome. Our binimal point is in the same location as when we started. The overflow

is basically into our sign bit.

We can perform subtraction in a couple of ways. The first (and maybe most obvious) is to take the two's

complement of the number to be subtracted and then perform an add. This is similar to my BCD examples in a

previous post. Of course, you do need to detect the sign of the answer or know it a priori, so you can convert it back

to sign-magnitude notation, which is really what my version of fixed-point is. In my case, I'm letting the synthesiser handle all that. I'm detecting beforehand in my add function the sign and magnitude of each addend, and I'm

performing ordered manipulations to ensure that no two's complement results occur.

Now, let's talk multiplication for a bit (no pun intended). Let's multiply two numbers: X and Y. If X is N bits in size,

and Y is P bits in size, then we'll need a field N+P bits wide to store the result. I'm sure there's a proof of this

somewhere, but for our purposes, we'll just take it on faith, OK?

Page 12: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 3 of 4

This could be problematic. In order to do this correctly, we would need to ensure that we properly align our binimal

points and then figure out the new binimal point location in the resulting register. Our solution simplifies this

greatly by assuming that all inputs to a given operation are the same length and have the same number of fractional

bits. If we can ensure that each input has N bits, then the output will have 2N bits. If each input has Q fractional bits,

then the output will have 2Q fractional bits. This is exciting stuff, because it makes life easy for us.

Page 13: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 4 of 4

This is especially handy because we generally need to hand back a result that matches the format as the one we

gave for input. When designing our system, we need to understand the largest value we'll ever expect to see. This

will define how many bits we need in the N field/register. Understanding this, we can easily determine the following

relationship.

Neat, huh? Now, let's talk about sign bits. The results of the multiplication are independent of the signs of the

numbers being multiplied, except for the sign of the answer. The absolute value of 5 x 5 = -5 x 5 = 5 x -5 = -5 x -5, and

the same goes for division. Thus, the only thing we need to check is if we multiplied (or divided) a negative and a

positive. This is the only case that gives a negative result. This can be easily determined by performing an XOR of

the sign bits. This is a good time to talk about binary multiplication and division. To date, I've kind of glossed over this function

by waving my hands and saying, "It's just repeated addition/subtraction." Well, that's true enough, but imagine if

we had 1 million divided by 1. We're well versed in our arithmetic principles, so we know right off the bat that the

answer is 1 million. However, our computing machinery only knows simple logic, so it will merrily perform 1

million and one adds to find the answer. That will take a lot of time.

Fortunately, there are implementable solutions in our logic to peform the multiplication in a single clock cycle. That

makes life easy for the multiplication (at least, with modern hardware and synthesisers). This being the case, I'll wave my hands and gloss over it, but you can search the internet for "Multiplication by shift and add" if you wish to

learn more.

I will assert that I have seen no good descriptions of fixed-point division (or, really, even binary division), so my

next column will be devoted purely to division. I only hope I can convey the ideas clearly. In the meantime, if you

would like to take a look at my code and use it for your own purposes, I have made it available here.

Actually, this reminds me of a conversation I had a long time ago when I was working for a defence contractor. I said something to the effect of "If world peace ever broke out, we'd be out of a job." My older (and wiser) coworker

responded, "Naaah, there's a lot of money to be made in verification." The moral of this story: Trust but verify. I

built my library on someone else's library. If you use the functions in my library, be sure to test them out thoroughly

to make sure they do what you intend. �

About the author

Tom Burke is Senior Systems Engineer.

Page 14: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 1 of 4

Performing math operations in FPGAs (Part

5)

This instalment discusses binary division.

By Tom Burke

Senior Systems Engineer

I am afraid to say that I lied to you in my previous article.

I said in that instalment would be the last of these rantings about binary math. But then I realised that I had no good

references on performing binary division (especially in fixed-point), so I decided to dedicate this installment to

binary division. I apologise in advance for the long-windedness of my pontifications. Fasten your seat belts! Let's first refresh ourselves on the "long division" that we learned in grade school. Let's say we want to find how

many threes are in 136 (136 ÷ 3) -- I don't know why we'd ever want to do this, but I'm sure there's a good

application for it somewhere. First, we set up our long division table as illustrated below.

Starting from the left (the higher-order digits), and working towards the right, we test how many times we can fit

our divisor into our dividend. So, left-aligning the two numbers, we see that 3 will go into 1 zero times (or, more

correctly, 300 will go into 100 zero times).

Next, we multiply the zero and our divisor, and put the properly-aligned result into the table beneath the dividend.

Now we subtract the results of the multiplication and place the result of the subtraction in the table -- perhaps carrying down only the next digit in significance.

Using the next-lower-ordered bit, we see that 3 will go into 13 four times (or, more correctly, 30 will go into 130

four times).

Page 15: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 2 of 4

We repeat this subtraction process until we've progressed through all of the digits.

Eventually, we discover that our result is 45 with a remainder or 1 (or 45 and 1/3). Of course, we can keep the

division going into those decimal places and generate a result or 45.33333333r (where the "r" indicated that the

preceding "3" will keep on recurring).

Of course, this is pretty easy for those of us that have been doing it for a while (my kids still struggle with it some),

but how is this process different from binary? Well, it's not, really, but there are a lot of "gotchas" when it comes to performing division using our computing machinery. Let's quickly use the same numbers to perform a binary

division as illustrated below.

Simple enough, if you give it some thought. But, there are a couple of "interesting" things here. The first thing, if you

notice, is that we are shifting the divisor to the right (÷2) each step. Since this is binary, this makes perfect sense.

Next, since we can only multiply by 1, all we ever need to do is set the appropriate quotient bit at the appropriate

time. Finally, as in the original base-10 case, if the dividend is greater than the divisor, then we subtract the divisor

from the dividend. So, we have the basic steps for performing binary division:

1. Bit-align the left-most "1" of the divisor with the left-most "1" of the dividend

2. Set all quotient bits to "0" 3. If the dividend ≥ divisor

4. Set the quotient bit that corresponds to the position of the divisor to "1"

5. Subtract the divisor from the dividend

Page 16: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 3 of 4

6. Shift the divisor 1 bit to the right (@divide;2)

7. If the leading bit of the divisor is at a position ≥ 0, return to step 3

8. Otherwise we're done

But how do we implement this in our hardware? Right off the bat, we can see some problems. Firstly (and what I see

as the "hardest") is this -- how do we align the left-most bits of the two numbers? It seems easy enough to say it, but

do we use a left-shift and compare? Do we use a giant multiplexer? The first method takes clock ticks, while the

second takes up lots of hardware.

Next, how do we keep track of the right number of zeroes at the front -- that is, how do we keep the bit position of

the quotient correct, and properly aligned? It's not impossible, of course, but things to think about. Lastly, what do

we do with a remainder? Do we just throw it away, or do we round?

So, let's take a look at an implementation for signed integer division. (Remember that this is my "spin" on things;

you might already have a better way of doing this.) I'm using sign and magnitude (no two's complement), so the first thing we'll do is ignore the sign bits, and make a register that is 2*(N-1) bits wide. We'll put the divisor (minus

the sign bit) into the left-most N-1 bits of this register. Next we create a quotient register that is N bits wide.

Now we do the division very similar as to the steps above, only we choose the bit of the quotient to flip (or not) by

the count, which we'll initially set to N-1:

1. Create register for quotient (N bits)

2. Create register for dividend (N-1 bits) 3. Create register for divisor (2N-2 bits)

4. Create register for count (I use N bits for expediency, but this is wasteful as count will always be less than

N)

5. Clear all registers (set them to zero)

6. Set count to N-1

7. Put the dividend (minus the sign bit) into the dividend register

8. Put the N-1 bits of the divisor (minus the sign bit) into the N-1 left-most bits of the divisor register 9. Right shift the divisor

10. Decrement the count

11. If Dividend ≥ Divisor

o Set the quotient[count] bit

o Subtract the divisor from the dividend

12. If count > 0; go to step 9

13. Quotient[N-1] (sign bit) = dividend[N] XOR divisor[N] 14. Done

As a side note, in purely integer division, the quotient will never be wider than the dividend (worst case is

divisor=1). You can find a proof of this elsewhere, I'm sure; but for now, let's take it on faith. I know this is as clear

as mud, so let's step through a quick example as shown below. In this example, N=8 (1 sign, 7 integer magnitude

bits), and we'll divide -57 by 3.

Page 17: Performing math operations in FPGAs - Fortronicold.fortronic.it/user/file/A&VElettronica/Performing math... · roll my own divider, ... Performing math operations in FPGAs (Part 2)

EE Times-Asia | eetasia.com Copyright © 2014 eMedia Asia Ltd. Page 4 of 4

This is fairly straightforward, I think, although it takes some time to wrap your head around. Also, I'm not sure this

is not the most efficient way to do the division (I'll let you research that), but it is deterministic. That is to say, it

always takes the same number of clock cycles to finish -- sometimes there's something very good to say about that.

But wait! We're not done here, yet (well, I'm not, anyway). The real problem becomes fixed-point math like I talked

about last time (which is also nearly exactly the same problem as with floating-point divides). Let's imagine, for a

moment, that we were using a Q=1 notation (S/MMMMMM.Q), so our numbers were not -57 and 3, but -38.5

(1/011100.1) and 1.5 (0/000001.1).

On the surface, it would seem that we should just be able to perform our division and assume that our answer will

be in the same format. Unfortunately, this is not true! If we look at our answer in that format, we see 1/001001.1, or

-9.5, but the answer should be -25.666. And if we were to use a Q=2 notation (S/MMMMM.QQ) then our problem

becomes 1/01110.01 (-14.15) ÷ 0/00000.11 (0.75) = 1/00100.11 (-4.75) -- again, completely incorrect! What's

going on, here? While it's not obvious from these examples, it turns out that using the fixed-point notation causes a bias of Q bits

(right-shifted) in your answer -- that is, we need to left-shift the final answer Q bits to get the correct answer.

Unfortunately, since we have truncated our answer, the results are badly skewed.

In order to fix this, we need to make some adjustments to our algorithm. The first is that we put the divisor (minus

the sign bit) into the N-1 bits of a register that is 2(N-1)+Q bits wide. Next, we need to put our dividend (minus the

sign bit) into the left-most bits of an N+Q bit wide register. Lastly, our quotient register also needs to be a minimum of 2(N-1) bits wide to hold a full answer (if we don't mind throwing away the remainder), or 2(N-1)+Q bits (if we

want to get some results in the fractional bits). Also, following the division, we probably need to do some error-

checking on the upper N-1 bits to detect an overflow. As an example, let's imagine that we were to divide 7.9375

(0/111.1111) by 0.0625 (0/000.0001). The answer, of course, is 127 (0/1111111.0000), which is an overflow.

I realise this I may not have made this horribly clear in my writing, so let's show another example; 1.1875

(0/001.0011) ÷ 0.25 (0/000.0100) = 4.75 (0/100.1100).

If you are interested, I have made my fixed-point library available here (at OpenCores.org). This library also

includes two multiply routines -- one that requires clocking and one that uses the synthesiser's ability to generate a combinational unit. As I said last time: "Trust but verify!" (Because, trust me, if you don't verify it for your use, it will

surely not work!)

Last but not least, as I'm sure that none of the above was horribly clear, I made a 53 minute video describing it.

So, after all of this, do you have any questions or comments? If so, please share them with the rest of us in the

comments section, and I'll try to iron out any areas that weren't clear. �

About the author

Tom Burke is Senior Systems Engineer.