Our ALU is required to support four integer arithmetic operations: Signed Addition (ADD), Unsigned Addition(ADDU), Signed Subtraction (SUB), and Unsigned Subtraction (SUBU).\ For signed operations, the inputs and output will be treated as 32-bit signed twos-complement integers.
We can make use of several properties of signed twos-complement numbers to significantly reduce the hardware requirements of our Aritmetic sub-block without significantly decreasing its speed. In fact, we can implement all four of the necessary operations using a single 32-bit adder!
Let's start by examining the signed vs. unsigned issue. MIPS Unsigned Addition/Subtraction is also referred to as Addition/Subtraction without overflow. The hardware for adding or subtracting twos-complement signed integers and magnitude representation unsigned integers is actually exactly the same. The only difference comes in determining whether or not Overflow occurs. Overflow is the condition where the result of the operations is either too large or too small to be represented in the alotted number of bits (32). For signed numbers, overflow can only occur when adding two numbers of the same sign or subtracting two numbers of opposite signs. It is indicated for addition by the result having the opposite sign of the inputs and in subtraction by the result having the opposite sign of the first input. In unsigned numbers, and overflow would be indicated by a carry-out from the most significant bit. However, since overflow would be ignored anyway in the situations where unsigned arithmetic is used (such as address computations), the MIPS instruction set indicates that overflow is never reported for unsigned arithmetic.
A trickier issue is how we can perform both addition and subtraction with just a single 32-bit adder. The answer comes by restating the operation: A - B = A + (-B). We can negate a twos-complement number by inverting the bits and adding one: (-B) = NOT(B) + 1. So, A - B = A + (-B) = A + NOT(B) + 1. By adding and inverter and a multiplexor (to select B or NOT(B)) in front of the B input we can implement A + B or A + NOT(B) easily. To add one, we take advantage of the fact that our adder has a carry-in. By carrying in a '1', we are effectively adding one to the result. Take it on faith for now that this trick also works out for unsigned (no overflow) subtraction.
To get a more detailed explanation of the ins and outs of binary arithmetic with twos-complement or magnitude unsigned numbers, refer to the beginning of Chapter 4 of Hennessy and Patterson's Computer Organization & Design.
To create the Arithmetic sub-block we will need to have a 32-bit adder with carry-in, carry-out, and overflow computation. Instead of going through the process of building this from scratch, we will be using an adder which has already been designed and instantiating it as a Component in our design.
You have already seen that the blue colored sub-blocks which you have placed in the ALU block diagram generate entity declarations with corresponding architectures created from their views which are then instantiated in the ALU architecture as components. In Renoir, however, these sub-blocks are not referred to as components because you cannot instantiate them in another design as they are; they are tied to one instance in one design. A sub-block can, however, be converted into a Renoir component, represented by a green colored box. They are essentially the same except for the fact that a component can be accessed from other design units to be instantiated.
Components allow for the creation of design libraries of commonly used objects which can be instantiated in new designs. I have created a 32-bit adder component and placed it in a library which will hold public design components that we will be using. Before we can use this adder in our ALU design, we will need to create a mapping to the new library.
The class design library will be called COELib and will be located at I:\1502\COELib. From the Design Browser, open the Library Mappings window from the Options menu. Left-click Add to bring up the Add Library Mapping window and fill in the appropriate data using the conventional subdirectories for source, hdl, and simulation data. When you are done, the window should look like Figure 1.
It is not necessary for a library to be open in the Design Manager in order to instantiate a component from it. In order to see what is in the library, though, go to the Open Library window from the File menu and open the COELib library. Expand the design tree in the source window to see a single design unit, in green, called Add32 with a symbol and single VHDL Architecture view called behav.vhd. The Design Manager window will look like Figure 2.
Now that we have mapped the COELib library, we can get started designing the Arithmetic unit. Open the ALU design from the Design Manager. Once you have done this, create a new block diagram view for the Arithmetic sub-block by right-clicking on the sub-block and selecting New View from the Open item on the pop-up menu. This will bring up the block diagram with ports seen in Figure 3.
Now we wish to instantiate an add32 component. Activate the
Add Component tool by selecting the button,
, from the toolbar. This
will bring up the Add Component window in Figure 4 which
will allow you to select which component to instantiate. In
the leftmost frame, select the COELib library. The
word COELib will appear in the box above this frame. In the
middle frame you will then see a list of the components in this
library, ADD32 will be the only one listed in this case.
Select the ADD32 component and ADD32 will appear
in the box above the middle frame. Also, since the View Type
setting in the lower right side is set to Default, the
box above the rightmost frame will be grayed out and the default
architecture for ADD32, which is behav.vhd, will be used.
Click OK when you have the proper component selected.
You will now see the message box in Figure 5 pop up asking whether you wish to add the package arithmetic.std_logic_arithmetic to your design. This is a VHDL package which is used in the adder. However, since there is nothing in the interface to the ADD32 component which requires this package (all ports in and out are in the std_logic_1164 package which is automatically declared) we do not need this package reference. Respond no to move on. You will now be see a shadow of the component as your pointer in the design area. Left-click somewhere in between the ports to place one instance of the component, then deactivate the tool.
In addition to the ADD32 component, there are several other tasks which need to be accomplished: selecting B or NOT(B) and deciding whether to carry-in a '1' to configure for addition or subtraction; deciding whether or not to compute overflow; and calculating Zero which is high whenever the result is equal to zero and low otherwise.
These tasks will be implemented using Embedded Blocks just like in the Logical sub-block. We need to place three Embedded Blocks:
One we will call TOBORBNOT and will place to the left of the ADD32 component. This will perform two functions, one is to create an inverted version of B and the other is to assign B to the internal signal BTEMP when we are adding (when ALUOp(1) = '0') and assign the inverted version of B when we are subtracting (when ALUOp(1) = '1').
The second embedded block will pass the OFL output of the adder to the Overflow output when we are performing the ADD or SUB operations (ALUOp(0) = '0') and '0' otherwise. Place this block to the right of and below the ADD32 component and call it OVERFLOW.
The last embedded block will be placed to the right of the ADD32 component and will be called CALCZERO. Its primary function is to set the Zero output high when the adder result is equal to zero and set it low otherwise. This requires the block to "read" the result coming out of the ADD32 component. Since it is not possible to "read" values from output only ports, we cannot directly connect the result ArithmeticR to the output of the adder. We must instead create an internal signal which can be "read" to determine zero. Thus, as a second function, this embedded block will assign the value of the internal signal S to the output ArithmeticR.
After adding these blocks your block diagram should look like Figure 6.
Now that the embedded blocks and the ADD32 component have been placed on the block diagram, connect the input and output ports and create the necessary internal signals so that the block diagram resembles Figure 7.
The final step, after all signals are connected properly is to define the behavior of the embedded blocks. The embedded VHDL text should be as follows:
TOBORBNOT :
BTEMP <= B WHEN ALUOp(1) = '0' ELSE NOT(B);
This embedded block implements a multiplexor with B as the input when the select, ALUOp(1), equals zero to implement and add and NOT(B) when the select equals one to implement a subtract. The other step necessary to implementing subtraction, carrying in a one, is taken care of by connecting ALUOp(1) to the carry-in of the adder. This way, when adding the carry-in is zero and when subtracting the carry-in is one.
OVERFLOW :
Overflow <= OFL AND NOT(ALUOp(0));
When ALUOp(0) = '0' we want to perform arithmetic with overflow. When ALUOp(0) = '1', we want to perform arithmetic without overflow. Thus, by ANDing the overflow output of the adder, OFL, with the inverse of ALUOp(0), which is '1' if we want overflow output and '0' if we do not, we achieve the desired results.
CALCZERO :
ArithmeticR <= S; Zero <= NOT(S(31) OR S(30) OR S(29) OR S(28) OR S(27) OR S(26) OR S(25) OR S(24) OR S(23) OR S(22) OR S(21) OR S(20) OR S(19) OR S(18) OR S(17) OR S(16) OR S(15) OR S(14) OR S(13) OR S(12) OR S(11) OR S(10) OR S(9) OR S(8) OR S(7) OR S(6) OR S(5) OR S(4) OR S(3) OR S(2) OR S(1) OR S(0));
This block contains two VHDL statements, necessary because we cannot simultaneously assign the output of the adder directly to ArithmeticR and at the same time use that value to determine the Zero output. The first statement takes the internal value of the adder output, S, and assigns it to the result, ArithmeticR. The second statement determines whether the adder output is equal to zero by ORing the individual bits together. If the result of the OR is '0', meaning the result is zero, we want to output a '1' and if the result of the OR is a '1', meaning the result is non-zero, we want to output a '0'. Thus we invert the result of the OR to obtain our Zero output.
Now that we have designed the Arithmetic sub-block, we must of course verify that it is functioning correctly. Since we are once again dealing with a fairly small design unit, we will test it by directly stimulating the signals in the simulator and observing the results in the Waves window.
You will need to create a simulation macro file which will run at least one set of vectors to test each possible outcome of each operation:
You may find it helpful when trying to create your test vectors to use the Windows NT Calculator utility which should be on the Accessories menu. This program allows you to convert between decimal and signed twos-complement binary representations of numbers.
Create and run your test macro to confirm the function of your Arithmetic unit. Comment your macro file to indicate the decimal equivalents of the test vectors as well as the expected values of ArithmeticR, Zero, and Overflow. Print out your macro file and waveforms and hand them in when you have finished with this unit.
When you have finished, you are ready to go on to Implementing the Comparison Sub-Block.