For a 32-bit machine, the smallest unsigned integer that can be represented is 0, and the largest unsigned integer that can be represented is 232-1. Additions that result in a ``too large'' number results in an overflow: adding 1 to 232-1 gives 0 -- the ``carry'' off the end of the word just disappears. For some machines / languages, arithmetic overflow causes an ``exception'', which if not handled will cause the program to abort. In C overflows are always ignored.
In 2's complement representation, negative numbers are bit patterns that have a leading 1 bit; this is called the ``sign bit''. The value of the bit pattern is determined by figuring out the corresponding positive number to which it can be added to result in a zero result due to overflow. The way we add is the same in unsigned arithmetic and in two's complement arithmetic: this is intentional, since we wish to use the same circuits on the chip to do addition for both number representations.
Using 16-bit registers, -3 is the binary number 1111 1111 1111 11012 or FFFD16, since
1111 1111 1111 1101 + 0000 0000 0000 0011 --------------------- 1 0000 0000 0000 0000is zero (with an overflow, which is intentionally ignored). Because addition and subtraction of 2's complement representation of numbers (signed integers) are done the same way as unsigned integers, the same ALU circuits -- and machine language instruction -- may be used. On some machines, there will be separate signed and unsigned arithmetic instructions; the only difference is whether overflows will cause exceptions, or what are considered to be overflow conditions.
To figure out the two's complement representation of a negative number, first write down the binary representation of the absolute value, complement all the bits (flip zero for ones and vice versa), then add one to the result. This is the two's complement representation of the number. For example, to find -6, we first write
0000 0000 0000 0110then complement to get
1111 1111 1111 1001and add 1 to get
1111 1111 1111 1010
We can verify that this is -6 by adding 6 to it:
1111 1111 1111 1010 + 0000 0000 0000 0110 --------------------- 1 0000 0000 0000 0000
The complement-all-bits-then-add-one procedure is actually what you do to negate a number in general. Try negating -6 above and verifying that you get 6.
In both the unsigned integer interpretation and two's complement interpretation of bit patterns, the number of different numbers that can be represented (in a 32-bit machine) is the same: 232. Above, we said that unsigned integers can take on the values [0,232-1] (0 through 232-1 inclusive). What are the values for two's complement notation? Are there more positive numbers than negative numbers in the set of numbers that can be represented in two's complement?
Consider the number (again using 16-bit registers now):
1000 0000 0000 0000It is negative, since the ``sign bit'' is set. What happens when you negate it using the complement-all-bits-then-add-one procedure? Do you get a positive number?
Consider the bit pattern
0111 1111 1111 1111as a two's complement representation of a number. This is the largest positive number that can be represented. What happens when you add 1 to it?
subz a,b,chas the same ``meaning'' as this C fragment:
mem[a] = mem[a] - mem[b]; if (mem[a] == 0) PC = c; else PC = PC + 1;where PC is the program counter, which is a register that contains the address of the current instruction.
In class, I went over the instruction sequence for adding two numbers, and the instruction sequence to implement a loop. You should look at the files ~/../public/add.oic and ~/../public/mult.oic to see how this is done. These files are commented the way I would expect your homework to be commented.
There is a one-instruction computer simulator. To try it out, run it on the input files:
% oic -v ~/../public/add.oicwhich gives verbose, instruction-by-instruction output as the program runs, or
% oic -s 6 -c 3 ~/../public/add.oicto run the program and printout 3 memory words starting at location 6. Similarly, if you run the multiplication program via the simulator as
% oic -s 0x100 -c 3 ~/../public/mult.oic ~/../public/mult.inp.oicyou'll just see the inputs and the result at addresses 0x100 through 0x102. The of the ``machine language'' input files are as follows: anything after a semicolon is ignored; the first non-comment line should contain the starting address into which the file is loaded into memory; all other non-comment lines should contain three numbers, each of which is a 16-bit number, separated by spaces, specifying the contents of a memory word.
If you had a macro assembler, you might define an add macro thus:
add: macro a,b,c,t subz t,t,next subz t,a,next subz t,b,next subz c,c,next subz c,t,next endmAfter this definition, occurrences of the line
add x,y,z,tempwould be replaced with the body of the macro definition, with appropriate textual substitutions for the arguments a,b,c,t. For example, if you had
subz x,t,next ; x = x - t. add x,y,z,temp ; z = x + y, using temp as scratch subz q,z,next ; q = q - zthe macro would be expanded, and the code would be treated as if the input was
subz x,t,next ; x = x - t subz temp,temp,next subz temp,x,next subz temp,y,next subz z,z,next subz z,temp,next subz q,z,next ; q = q -z
We are not using a macro assembler in this class. For the one-instruction computer simulator, there is no assembler at all! You must ``hand assemble'' your code into the simulator's input .
The loop example that I gave in class was
subz i,i,next ; i = 0; subz negmax,negmax,next ; do { subz negmax,max,next loop: ... ; loop body ... subz i,neg1,next ; i = i + 1; subz t,t,next ; } while (i != max); subz t,i,next subz t,negmax,done subz t,t,loop done: ... ; code that follows loopThe loop in the mult.oic example is different than that given in class. Why?
bsy@cse.ucsd.edu, last updated