Modified version Introduction to Binary Computers Steve Jost January 1997 Table of Contents 1. A Brief History of Computers 1 2. Binary and Hexadecimal Numbers 2 3. Decimal-to-Binary Conversion 3 4. Binary Arithmetic 5 5. Representation of Negative Numbers 7 6. Bitwise Operations 8 7. Introduction to MACHINE-16 9 8. Addressing Modes 15 1. A Brief History of Computers. 1200 The modern abacus was invented. 1642 Blaise Pascal invented the first calculating machine at the age of 19. 1672 Gottfried Leibniz built a calculating machine that could add, subtract, multiply, and divide. 1837 Charles Babbage built the first mechanical computer capable of multistep programs. This machine was about 100 years ahead of its time and little further progress was made until the 20th century. 1842 Probably the first programmer was Ada Augusta, the Countess of Lovelace. She worked with Babbage helping him write programs for his “analytical engine.” She noted that the machine could not “originate anything” but could only do “what we know how to order it to perform.” The computer language ADA developed in the 1970's was named after her. 1887 Dr. Herman Hollerith developed a punched card reading machine to assist in tabulating data for the 1890 U. S. census. Without this machine, processing this data would have taken more than ten years! With the machine, the data processing for the census only took three years. 1937 Howard Aitken who was a professor at Harvard built the Mark I digital computer. It was controlled with electromagnetic relays and received its input from punched cards. 1939 The first electronic computer using vacuum tubes for switching was constructed by Dr. John Vincent Atanasoff at Iowa State college and was called the ABC (Atanasoff-Berry Computer). It was built for solving systems of simultaneous equations. 1940 The first general purpose computer was built by Atanasoff together with John Mauchly and J. Presper Eckert. This computer was called ENIAC (Electronic Numerical Integrator and Calculator) and funded by the U.S. army. It could do 300 multiplications per second which was 300 times faster than any other machine of the day, but is contained 18,000 vacuum tubes and weighed 30 tons. The ENIAC was programmed by externally manipulating plugs and switches. The machine was used by the army until 1955. 1 1945 John von Neumann proposed two ideas which made modern high speed computers possible. These ideas were using binary numbers to store data, and storing instructions as data rather than entering them by switches or plugs as was done in earlier machines. 1949 The transistor was invented by Bardeen, Braitain, and Shockley at Bell Telephone Laboratories, who shared the Nobel Prize for this invention which made modern electronics possible. 1949 The first stored program electronic computer was developed at Cambridge University. It was built by M.V. Wilkes and called EDSAC (Electronic Delay Storage Automatic Calculator.) 1950 Computer industry forcasters concluded that about 10 stored memory computers would meet the demand for the entire U.S for years to come. This turned out to be one of the worst forecasts in history. 1954 International Business machines (IBM) sold the first computer for record keeping and business organization. This machine was less expensive than other computers available at the time and was widely accepted. These early computers were called first generation computers. They were programmed in binary machine language which a difficult and error prone process. First generation computers were originally designed for scientific operations. 1952 The first high level language was invented by Dr. Grace Hopper. She developed a compiler for the language called A-2 which converted instructions into machine language. 1954 The FORTRAN (FORmula TRANslator) language was developed at IBM by a programming team headed by John Backus. FORTRAN is still widely used for scientific applications. 1959 The first second generation computers were introduced which were smaller and faster than the first generation ones. They used solid state devices such as diodes and transistors instead of vacuum tubes. In addition computers which accepted instructions in high level languages became widespread. 1959 To meet the increasing demand for data processing, the language COBOL (COmmon Business Oriented Language) was developed. This language became popular because it was written in a quasi-English form that could be more easily understood by nonprogrammers than other languages of that time. 1963 The BASIC (Beginners All Purpose Symbolic Instruction Code) language was developed at Dartmouth college by John Kemeny and Thomas Kurtz. It was introduced as a language which was easy for students to learn, and was available on a timesharing computer which allowed several users to take turns sharing the central processing unit of the computer. 1970 The C programming language was designed by Dennis Ritchie. Its name comes from the fact that it was an improved version of the language BCPL or B for short. Many applications which formerly were written in assembly language are now written in C. 1976 The first Cray supercomputer was built. It is capable of performing more than 100 million floating point operations per second (100 megaflops). Computers with the capability of performing more than 10 billion floating point operations per second (10 gigaflops) will be possible within the next ten years. 2. Binary Numbers for Computers Many electronic hardware devices have two natural states such as conducting or not conducting, magnetized or not magnetized, positive or negative, on or off, etc. Using the binary numbers 0 or 1 greatly simplifies the design of electronic computers. However, because long sequences of 0's and 1's are difficult to read and understand, binary digits are conventionally grouped to make them more comprehensible. The following table shows the terms that are sometimes used to denote various sized groups of binary digits. 2 Number of Binary Digits ======================= 1 4 8 16 32 Term ==== bit nibble byte word longword Here are some of the common C datatypes and their properties. C Datatype char unsigned char short int unsigned short int long int unsigned long int float double Number of bits 8 8 16 16 32 32 32 64 Number of bytes 1 1 2 2 4 4 4 8 Minimum Value -128 0 -32,768 0 -2,147,483,648 0 -3.40 x 10^38 -1.79 x 10^308 Maximum Value 127 255 32,767 65,536 2,147,483,647 4,294,967,296 3.40 x 10^38 1.79 x 10^308 The size of the datatype int depends on the particular implementation of C being used. On a mainframe or minicomputer, the likely size of an int is 4 bytes, while on a personal computer or microcomputer, its size is probably 2 bytes. As an introduction to digital computers, we will study a simplified computer called MACHINE-16 which only uses numbers of length 8 bits or 1 byte. The reason for the name MACHINE-16 is that it has 16 instructions for manipulating data. MACHINE-16 is a Von Neumann machine, described in Section 1, in the sense that both data and instructions can be stored in its memory. The instructions are executed sequentially one at a time, with the possibility of looping back to repeat a block of instructions several times. Although modern assembly languages are much more powerful than the language of MACHINE-16, the 16 instructions in its instruction set are powerful enough to solve a wide variety of problems. Some of these problems will be discussed in Section 6. 3. Decimal-to-Binary Conversion Because MACHINE-16 uses 8 bit binary arithmetic, all of our examples will be with 8 bit numbers. Initially, we will only consider positive or unsigned binary numbers. Later, we will see how negative numbers can be represented with eight bits. To convert a binary number into base 10 or vice versa, it is convenient to use the Binary-Hex-Decimal conversion table shown in below. A binary 1 means that that power of two is present while a 0 means that it is absent. Here is a table showing the various powers of two that we will need: Binary ======== 1 10 100 1000 10000 100000 1000000 10000000 Decimal ======= 1 2 4 8 16 32 64 128 For example 01001101 = 64 + 8 + 4 + 1 = 77. 3 To convert a decimal number to binary, the procedure is reversed: express the decimal number as a sum of powers of 2 and then write this sum as a sequence of binary bits. For example, to convert the decimal number 77 into binary, we write 77 -64 -13 - 8 -5 - 4 -1 - 1 -0 We select for each subtraction, the largest power of 2 which is less than or equal to the amount remaining. We then represent those powers of 2 present as 1 and those powers of 2 missing as 0. This gives 64 + 8 + 4 + 1 = 01001101. Because long binary numbers are hard to read, computer programmers prefer to express them in a form that is more easily read. The most common choice of notation today is the hexadecimal (base 16) representation where each four bit group of digits (nibble) is represented by a single hexadecimal digit. These digits are shown in the following Binary-Hex-Conversion table. Because we will be using the hex representation for machine code input to MACHINE-16, it is essential that this table be memorized. Binary Hex ====== === 0000 0 0001 1 0010 2 0011 3 0100 4 0101 5 0110 6 0111 7 1000 8 1001 9 1010 a 1011 b 1100 c 1101 d 1110 e 1111 f Decimal ======= 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 To represent a binary number in hex notation, simply replace each nibble by its corresponding hex digit: 0110 6 1001 9 1110 e 1001 9 0100 4 1100 c 0101 5 0001 1 The binary numbers we consider will be 8 bit numbers, each of which can be represented by 2 hex digits. We will employ the C language representation which uses the prefix “0x” (zero x) for hex numbers. For example the expression 0x5d represents the binary number 01011101. It is important to note that the internal binary representation of an integer is independent of the form that the number takes when it is printed. The binary number 01011101 looks like 93 when printed with the C++ manipulator dec , like 5d when printed with hex, and like ]when printed as a regular char. Exercises 4 Assume that all binary numbers consist of 8 bits and are unsigned, that is, are in the range 0 to 255. Signed binary numbers are discussed in Section 5. 1. Convert the following binary numbers to decimal: 01101011, 01110000, 00000111, 11111111, 01010101 2. Convert the following decimal numbers to binary: 49, 7, 153, 200, 191, 128, 93 3. Convert the following hex numbers to binary: 0x41, 0x3f, 0x83, 0xef, 0x20, 4. Convert the following binary numbers to hex: 1101101, 11101101, 11010000, 0x2a, 00000111, 0xbb 11111111 5. Binary Arithmetic The four basic operations addition, subtraction, multiplication, and division are all easily performed with binary numbers. In fact they are actually easier to perform in binary because the addition and multiplication tables are so small. Here are basic addition facts in binary. 0 0 1 1 + + + + 0 1 0 1 = 0 = 1 = 1 = 10 0 0 1 1 x x x x 0 1 0 1 = = = = /* Result is 0, carry 1 */ 0 0 0 1 Carrying and borrowing are also performed as in decimal arithmetic. To add the numbers 0x3a and 0x27, first convert to binary, and add from right to left, carrying 1 to the next column when the result is 10. 58 +39 -97 0x3a +0x27 ---0x61 ---> ---> <--- 00111010 00100111 -------01100001 To subtract two binary numbers, borrow 1 when the bottom digit is 1 and the top digit is 0. For example, to compute 0x3a - 0x27, 5 58 -39 -19 0x3a - 0x27 ---0x13 ---> ---> <--- 00111010 00100111 -------00010011 Binary multiplication is also similar to decimal multiplication. The top factor is used as a partial product for each binary 1 in the bottom factor. The top factor is shifted by as many places to the left as the power of 2 that the binary 1 represents. To multiply 0x1e by 0x06, 20 x 6 --- 120 0x14 x 0x06 ---- 0x78 ---> ---> 00010100 00000110 -------000101000 000101000 -----------<--- 0001111000 Shift by 1 bit Shift by 2 bits Only the rightmost eight bits are retained in the answer. Any bits to the left of these 8 bits are called overflow bits. In case any of the overflow bits are nonzero, an overflow has occurred and the rightmost eight bits which are retained in the answer are invalid. Because integer arithmetic operations are not checked for overflow in the C language, it is possible to obtain an incorrect answer when two numbers are multiplied or added. For example, when we multiply 58 and 39 and we store the result as unsigned char (8 bits), we obtain the answer 214 when the actual answer is 2262. The following calculation shows why: 58 x 39 -- 0x3a x 0x27 ---- ---> ---> 00111010 00100111 -------00111010 00111010 00111010 00111010 ------------0100011010110 Retain eight low order bits Only the rightmost eight bits 11010110 (hex 0xd6 or 214 decimal) are retained. Division is also performed as it is for decimal numbers. Unlike base 10 long division, no guesswork is involved to decide how many times the divisor goes into the trial dividend. Either it goes (quotient = 1) or it doesn't (quotient = 0). As an example, divide 213 (binary 1101010, hex 0xd5) by 9 (binary 00001001, hex 0x09). 10111 -------1001 ) 11010101 10010000 -------1000101 100100 ------1111 1001 ---110 <-- quotient <-- remainder The answer is 23 (binary 00010111, hex 0x17) with a remainder of 6 (binary 00000110). Exercise 6 Convert the following hex numbers to binary, perform the following operations, and convert back. 0x45 + 0x3f, 0x5a + 0x74, 0xe2 - 0xaf, 0xa4 - 0x9c, 0x0a x 0x2f, 0x47 x 0x1b, 0xc4 / 0x2e, 0xbe / 0x19 5. Representation of Negative Numbers To represent negative numbers, we use the analogy of an automobile odometer. Because an odometer display has only five decimal digits to the left of the decimal point, only numbers in the range 0 to 99999 can be shown even though the actual number of miles may be much greater. The number 0 represents 0, 100000, 200000, etc., but it can also represent the negative numbers -100000, -200000, etc. The number 99999 represents 199999, 299999, and also -1, -100001, -200001, etc. By adopting the convention that 1 to 99999 represent positive numbers and 50000 to 99999 represent negative numbers, all numbers from starting from -50000 to 49999 can be represented. In the case of 8 bit binary numbers, we let 00000000 through 01111111 represent positive numbers and 10000000 through 11111111 represent negative numbers. By this convention, if the leftmost bit is 0, the number is positive, and if the leftmost bit is 1, the number is negative. In this way, an 8 bit binary number can represent all integers between -128 and 127. Such a representation is called a signed integer. The following table shows how to represent negative numbers in binary. Binary 01111111 01111110 .. 00000010 00000001 00000000 11111111 11111110 .. 10000001 10000000 Hex 7f 7e .. 02 01 00 ff fe .. 81 80 Decimal 127 126 .. 2 1 0 -1 -2 .. -127 -128 Converting a an eight bit binary positive number into the corresponding negative one involves subtracting it from 100000000. (The eight bit representation of 100000000 is 00000000.) For example, to convert 0x05 = 00000101 to a negative number, subtract it from 100000000 to obtain 11111011. This operation is called obtaining the two's complement of a binary number. In general the two's complement of a binary number is formed by subtracting it from 2n where n is the number of bits. Another way of performing the two's complement is to complement each digit (replace 0 by 1 and 1 by 0 ) and add 1 to the result. The complement of 00000101 is 11111010 and adding 1 produces 11111011. To verify that these numbers are actually negatives of each other, we can add them to see that the sum is 100000000 = 00000000 when truncated to eight bits. The beauty of two's complement arithmetic is that no special rules are needed to accommodate negative numbers. Merely perform the operation and keep the rightmost eight bits. Here are some examples: 7 - 29 + 42 ---13 -29 - (-42) ----13 (-6) x (-5) ----- 30 <--- ---> ---> <-----> ---> <--- 11100011 00101010 -------00001101 11100011 11010110 -------00001101 ---> ---> 11111010 11111011 -------...111111111111010 ...11111111111010 ...111111111010 ...11111111010 ...1111111010 ...111111010 ...11111010 --------...000000000011110 retain 8 bits 6. Bitwise Operations In addition to the 4 basic operations discussed in Section 3, C also allows the following operations: bitwise and ( & ), bitwise or ( | ), bitwise exclusive or ( ^ ), and bitwise complement (~). These bitwise operations are defined by the following tables. x 0 1 0 1 y 0 0 1 1 x&y 0 0 0 1 x 0 1 0 1 y 0 0 1 1 x|y 0 1 1 1 x 0 1 0 1 y 0 0 1 1 x^y 0 1 1 0 x 0 1 !x 1 0 8 For eight bit binary numbers, these operations are performed on each column of bit pairs separately. For example, to compute 52 & 45, first convert to binary, perform the bitwise and 00110100 & 00101101 = 00100100, and convert back to decimal which gives 36. The bitwise and is useful for isolating certain bits in a binary number to use in further computations. In the implementation of MACHINE-16 which is discussed in Sections 7 to 9, an eight bit machine instruction can be broken down into two parts, the opcode which consists of bits 1 to 4, and the addressing mode which consists of bits 7 and 8. (Bits 5 and 6 will not be used in our implementation.) To isolate the opcode, we can perform a bitwise and with the mask 0xf0 = 11110000. The bits 1 to 4 will remain unchanged, but the bits 5 to 8 will be changed to zero. This can be performed by the C statement opcode = instruction & 0xf 0; To obtain the addressing mode, use the statement mode = instruction & 0x03; because 0x03 = 00000011. Exercises 1. Find the values of the following expressions: 0x75 & 0xfb, 0x3c | 0xe9, 0xfa ^ 0xa5, ~c7 2. In a hypothetical operating system, the bits 5 to 27 in the the 4 byte integer variable psw (processor status word) represent the state of the system. The 32 bits are numbered from 0 to 31 from left to right. Write a hex mask which will isolate these bits (Hint: First write the mask in binary). 7. Introduction to MACHINE-16 The MACHINE-16 is a simple hypothetical stored memory computer which has 64 bytes of memory and 16 instructions. Its capabilities are comparable to the earliest computers built in the 1930's, but it is thousands of times faster because it will be implemented on a modern computer. The memory can be thought of as an array of bytes (sometimes called cells) with addresses numbered 0x00 to 0x3f. This memory array will be represented by the global array of char. We will refer to each byte as a memory cell which will be abbreviated as MC. Here is an example: 01010110 01001001 00101001 00101001 01001010 10010100 ... MC 0 MC 1 MC 2 MC 3 MC 4 MC 5 ... Memory cells with addresses 0x00 and 0x01 (referred to in the C program as mem[0] and mem[1]) each have a special purpose. The memory cell with address 0x00 is called the program counter (PC) and is used to hold the address of the instruction which is currently executing. (This will make sense later when you see what the instructions are.) The memory cell with address 0x01 is called the accumulator (AC) and is used to hold the result of an arithmetic operation. A MACHINE-16 instruction consists of two bytes (four nibbles or 16 bits). The first nibble is the opcode and the second nibble is the addressing mode. The table below describes the 16 MACHINE-16 opcodes. In these descriptions, PC refers to the program counter, AC refers to the accumulator. The opcodes are referred to as 0x00, 0x10, 0x20 rather than 0x0, 0x1, 0x2b because the second nibble is cleared to zero using a bitmask (see Section 9). 9 Opcode 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 Mnemonic STOP LOAD STORE CLEAR INCR DECR ADD SUBT MULT DIV JUMP 0xb0 BGZ 0xc0 0xd0 0xe0 0xf0 PRIND PRINC SCAND SCANC Meaning exit(1); AC = MC; MC = AC; MC = 0; MC++; MC--; AC += MC; AC -= MC; AC *= MC; AC /= MC; Jump to address in MC If AC > 0 jump to address in MC Print MC as int Print MC as char Read MC as int Read MC as char The second byte (nibbles 3 and 4) of the instruction determines the memory cell (MC) which contains the information to be used by the instructions 0x00, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, and 0xd0. It can also determine the address where information is to be put (instructions 0x20, 0x30, 0xe0, 0xf0). In the preceding table of operands, the symbol = means assignment as in C++, ++ means increment, and -- means decrement. The symbols +=, -=, *=, and /= have the same meanings as the corresponding C++ assignment operators. We distinguish between assignment (A=5;) and initialization (char A=5;) in exactly the same way that the C++ language does. The addressing mode is the second nibble of the instruction and determines how to find the memory cell on which the instruction operates MC from the operand. The four possible addressing modes are shown in the following table, and are discussed in Section 8. Addressing Mode 0x00 0x01 0x02 0x03 Mode Name IMMEDIATE DIRECT INDIRECT DINDIRECT For simplicity, we only discuss the IMMEDIATE addressing mode in this section, which has the code 0x00. If the addressing mode is IMMEDIATE, the address of MC is one greater than the address of the current instruction. In other words, the operand contains literally the value to be supplied to the instruction. In the IMMEDIATE mode, the operand is used as a literal constant. For example, the following diagram shows the memory cells 0x00 to 0x07. Since the value of PC is 0x03, the instruction in cell 0x03 executes. The left nibble is 0x6 which means ADD. The right nibble is 0x0 which means IMMEDIATE addressing mode. We denote the opcode as 0x60 to show that the addressing mode has been zeroed out and the addressing mode as 0x00. Next the PC is incremented by one byte so that it has the value 0x04 and it contains the address of the operand. Since the operand contains 0x05, this literal value is added to AC which contains 0x07, so the new value of AC is 0c or decimal 12. 00 01 02 03 04 05 06 07 opcode: address byte 03, nibble 1, value 60 +--+--+--+--+--+--+--+--+ mode: address byte 03, nibble 2, value 03 |03|07|00|60|05|00|00|00| operand: address byte 04, value 05 10 +--+--+--+--+--+--+--+--+ Even though the IMMEDIATE mode is a legal addressing mode for all instructions, there are 3 instructions for which this mode is not generally useful. These instructions are STORE, SCAND, and SCANC. These three instructions all deposit a value in MC which will overwrite the operand if the mode is IMMEDIATE. The instructions INC and DEC are also not very useful in IMMEDIATE mode because they change the value of the operand. For these instructions one of the other operating modes discussed in Section 8 is more appropriate. We now look at two examples of programs written in the MACHINE-16 language. These examples use only the IMMEDIATE addressing mode. Program 1: Add the numbers 5 and 7 and put the result in AC. The sequence of instructions is LOAD 5 IMMEDIATE ADD 7 IMMEDIATE STOP 0 Load the literal value 5 into AC Add the literal value 7 to AC, the answer is in AC Stop execution, 0 is a dummy operand. To actually run program 1 (see Source Code for Program 1 below) on the MACHINE-16 simulator we need to translate the mnemonic instructions into hex numbers. These numbers will occupy the first two characters of each line of the source file. The source code contains six columns: the machine instructions and operands, the address, the label, the mnemonic, the addressing mode, and the equivalent C instruction. Of all these columns, only the first is read by MACHINE-16. There must also be no blank lines in the source code. As mentioned earlier, the first memory location (address 0x00) is the program counter (PC) and the second location (address 0x01) is the accumulator (AC). (Pay special attention to this point. A common mistake when writing MACHINE-16 programs is to begin numbering the addresses at 0x01 rather than at 0x00.) The PC is initialized to the starting address of the program. For this example, we start the program at address 2, immediately after the PC and the AC. The accumulator AC can be initialized to any value, say 0. The complete source code for program 1 is shown is as follows: Source Code for Program 1: Add the numbers 5 and 7 and put the result in AC. 02 00 10 05 60 07 00 00 00 01 02 03 04 05 06 07 PC: START AC: 0 START: LOAD 5 ADD 7 STOP 0 IMMEDIATE char PC = START, char AC = 0; AC = 5; IMMEDIATE AC += 7; IMMEDIATE exit(1); To run this program use a text editor to create a file named file.sou. You can replace the name file with any name you want, but you must have the .sou extension which indicates that it is source code for MACHINE 16. Then type mach file at the DOS prompt to run the machine. See your instructor for details of how to obtain the executable for MACHINE 16. Dump for Program 1. 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ 00 | 02 00 10 05 60 07 00 00 00 00 00 00 00 00 00 00 | 11 10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 30 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ MC MC COUNTER PC INSTRUC OPCODE MODE PC ADDRESS CONTENTS ======= == ======= ====== ==== == ======= ======== 1 02 10 10 00 03 03 05 2 04 60 60 00 05 05 07 3 06 00 00 00 07 07 00 00 10 20 30 AC == 05 0c 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 07 0c 10 05 60 07 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ The dump will be written to the file named file.dmp. The dump shows the state of memory immediately after the instructions are loaded from the source file. The contents of all 64 memory locations (0063) are shown. To find, for example, the contents of the cell with address 0x2a, look in the row 20 and the column a. We see that the contents are 00. After the initial dump, a detailed program trace is shown. The meanings of each column in the trace are as follows: the COUNTER sequentially numbers the instruction executions. In the current implementation of MACHINE-16, a maximum of 200 instruction execution steps are allowed to prevent infinite loops which will fill up the file system. The PC is the current value of the program counter. A valid program may also exceed the 200 step limit, but this will be rare because the programs we will be executing are short. This instruction is then broken up into the OPCODE and the addressing MODE which are shown next. If the addressing mode is IMMEDIATE, the PC is now incremented by 1 to read the operand, so PC is listed again to be sure that its value has actually changed. If the value of MODE is 0 (IMMEDIATE), the value of this address is set to be the current value of the PC because this is the address containing the operand. Finally the value of the accumulator AC is shown. The value of AC is not shown for the STOP instruction 3 because the program exits before the final value of AC is printed. After the program terminates because of the STOP instruction, a final dump is shown of the memory locations immediately after program termination. In this case, the dump is identical to the preliminary dump except for the change in the value of AC; it has been changed from 0x00 to 0x0c (decimal 12), the result of 5+7. This shows that the program has performed correctly. The dump and program trace are valuable tools for debugging MACHINE-16 programs. 12 Program 2: Print the message HELLO. MACHINE-16 is not limited to numeric computations. The Program 2 shows how to use it to print the message “HELLO” to the screen. A new line character (ASCII code 0x0a) is printed at the end of the message so that the prompt ends up at the beginning of the next line. The dump shows that the only memory location which has changed is the PC which has changed from its initial value 0x02 to 0x0f, the address after the STOP instruction. Use the following table assist you in translating characters into their hexadecimal ASCII codes. A Table of ASCII Codes. char dec hex \0 0 00 ^A 1 01 ^B 2 02 ^C 3 03 ^D 4 04 ^E 5 05 ^F 6 06 \a 7 07 \b 8 08 \t 9 09 \n 10 0a \v 11 0b \f 12 0c \r 13 0d ^N 14 0e ^O 15 0f ^P 16 10 ^Q 17 11 ^R 18 12 ^S 19 13 ^T 20 14 ^U 21 15 ^V 22 16 ^W 23 17 ^X 24 18 ^Y 25 19 ^Z 26 1a ^[ 27 1b ^\ 28 1c ^] 29 1d ^^ 30 1e ^_ 31 1f char dec hex 32 20 ! 33 21 " 34 22 # 35 23 $ 36 24 % 37 25 & 38 26 ' 39 27 ( 40 28 ) 41 29 * 42 2a + 43 2b , 44 2c 45 2d . 46 2e / 47 2f 0 48 30 1 49 31 2 50 32 3 51 33 4 52 34 5 53 35 6 54 36 7 55 37 8 56 38 9 57 39 : 58 3a ; 59 3b < 60 3c = 61 3d > 62 3e ? 63 3f char dec hex @ 64 40 A 65 41 B 66 42 C 67 43 D 68 44 E 69 45 F 70 46 G 71 47 H 72 48 I 73 49 J 74 4a K 75 4b L 76 4c M 77 4d N 78 4e O 79 4f P 80 50 Q 81 51 R 82 52 S 83 53 T 84 54 U 85 55 V 86 56 W 87 57 X 88 58 Y 89 59 Z 90 5a [ 91 5b \ 92 5c ] 93 5d ^ 94 5e _ 95 5f 13 char ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ ^? dec hex 96 60 97 61 98 62 99 63 100 64 101 65 102 66 103 67 104 68 105 69 106 6a 107 6b 108 6c 109 6d 110 6e 111 6f 112 70 113 71 114 72 115 73 116 74 117 75 118 76 119 77 120 78 121 79 122 7a 123 7b 124 7c 125 7d 126 7e 127 7f Source Code for Program 2: Print the Message “Hello”. 02 00 d0 48 d0 45 d0 4c d0 4c d0 4f d0 0a 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f PC: AC: START: START 0 PRINC 'H' PRINC 'E' PRINC 'L' PRINC 'L' PRINC 'O' PRINC '\n' STOP #0 IMMEDIATE IMMEDIATE IMMEDIATE IMMEDIATE IMMEDIATE IMMEDIATE char PC = START, AC = 0, cout << ‘H’; /*'H' == 0x48*/ cout << 'E'; /*'E' == 0x45*/ cout << 'L'; /*'L' == 0x4c*/ cout << 'L'; /*'L' == 0x4c*/ cout << 'O'; /*'O' == 0x4f*/ cout << '\n'; /*'\n'== 0x0a*/ exit(1); Dump for Program 2 00 10 20 30 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 02 00 d0 48 d0 45 d0 4c d0 4c d0 4f d0 0a 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ COUNTER ======= 1 2 3 4 5 6 7 00 10 20 30 PC INSTRUC OPCODE MODE == ======= ====== ==== 02 d0 d0 00 04 d0 d0 00 06 d0 d0 00 08 d0 d0 00 0a d0 d0 00 0c d0 d0 00 0e 00 00 00 PC ADDRESS CONTENTS == ======= ======== 03 03 48 05 05 45 07 07 4c 09 09 4c 0b 0b 4f 0d 0d 0a 0f 0f 00 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 0f 00 d0 48 d0 45 d0 4c d0 4c D0 4f d0 0a 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ 14 AC == 00 00 00 00 00 00 Exercises 1. Write a program which will compute the expression (-2+8)*(3+13) and print out the answer. 2. Print out a message of your choice. 8. Addressing Modes We have already discussed the IMMEDIATE addressing mode in the last section. In this section we introduce the DIRECT and INDIRECT addressing modes. The following Figure illustrates the four addressing modes. Recall that the opcode 0x60 means ADD. In this Figure, 6 is the opcode and 0 is the addressing mode, so the operand 02 is contained in the address immediately following the instruction in the address 04, which means that the literal constant 02 is added to the value of AC which is 05, changing AC to 07. Here are illustrations of the four addressing modes: In each case, the addressing mode is the second nibble of the byte at address 0x03. 00 01 02 03 04 05 06 07 +--+--+--+--+--+--+--+--+ |03|05|07|60|02|06|02|05| IMMEDIATE +--+--+--+--+--+--+--+--+ PC AC ^ 00 01 02 03 04 05 06 07 +--+--+--+--+--+--+--+--+ |03|05|07|61|02|06|02|05| DIRECT +--+--+--+--+--+--+--+--+ ^ | +----+ 00 01 02 03 04 05 06 07 +--+--+--+--+--+--+--+--+ |03|05|07|62|02|06|02|05| INDIRECT +--+--+--+--+--+--+--+--+ |^ | ^ |+----+ | +---------------+ 00 01 02 03 04 05 06 07 +--+--+--+--+--+--+--+--+ |03|05|07|63|02|06|02|05| DINDIRECT +--+--+--+--+--+--+--+--+ |^ | ^ |^ |+----+ +----+| +---------------+ To implement variables, the DIRECT addressing mode is most useful. In this case, the operand does not contain a literal constant, but the address of a variable. The operand contains 02 which is used as an address to obtain the value 07 which is in memory cell 02. This value 07 is now added to the value 05 in AC to obtain 0c (decimal 12). When pointers are necessary, we use the INDIRECT addressing mode, which uses the address stored at the opcode as the address containing the value of interest. In other words, you don't use the address contained in the operand to find the value of interest, you use the address in the operand as the address of 15 the value you want, just like ordinary pointers in C++. The mode 02 denotes INDIRECT addressing, the operand contains the address 02, memory location 02 contains the address 07 which contains the value 05. This is added to the 05 in AC to produce 0a. The DINDIRECT (double indirect) addressing mode which is needed to implement double pointers (declared as char **p;. or char *p[]; in C++). Double pointers are used in situations such as the address of the beginning of an array of pointers. We will not discuss the DINDIRECT addressing mode in CSC310. Program 3: Adding two variables using the DIRECT addressing mode. This program which initializes variables to values of 5 and 7, adds the contents of these variables, and prints the result as a decimal number. The DIRECT mode must be used because the operand of the ADD command does not contain the literal value to be added, but contains the address of the variable which contains the value. This output is printed at the screen, even though the dump is sent to the dump file. If you want to save the output in a file, redirect the output as usual from the DOS prompt mach file > file.out Source Code for Program 3: Adding Two Variables using DIRECT addressing mode. 04 00 05 07 11 02 61 03 c1 01 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b PC: AC: X: Y: START: START 0 5 7 LOAD X ADD Y PRIND AC STOP 0 DIRECT char char char char AC = PC = START; AC = 0; X = 5; Y = 7; X; DIRECT AC += Y; DIRECT cout << int(AC); IMMEDIATE exit(1); The label X denotes the address of the variable X, which is 0x02. The address of Y is 0x03. This example illustrates that MACHINE-16, like other Von Neumann digital computers, makes no distinction between data and instructions in its memory cells; everything is merely eight bit binary numbers. It is up the program to interpret these numbers. The dump is shown in next and the output from the program to the screen is 12, which is 5+7. 16 Dump for Program 3: 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ 00 | 04 00 05 07 11 02 61 03 c1 01 00 00 00 00 00 00 | 10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 30 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ MC MC COUNTER PC INSTRUC OPCODE MODE PC ADDRESS CONTENTS ======= == ======= ====== ==== == ======= ======== 1 04 11 10 01 05 02 05 2 06 61 60 01 07 03 07 3 08 c1 c0 01 09 01 0c 4 0a 00 00 00 0b 0b 00 00 10 20 30 AC == 05 0c 0c 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 0b 0c 05 07 11 02 61 03 c1 01 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ Program 4: Adding Together the Elements of an Array. The fourth example of a MACHINE-16 program is one that adds together all the elements of an array. To reference the elements of the array, the pointer P is used which is initialized to the address ARRAY and then is incremented so that it points to each of the array elements in turn. The command BGZ in address 0x16 checks to see if P is beyond the end of the array, and if it is, the program prints the value of the sum and stops. Note that the INDIRECT addressing mode is used in the ADD command in address 0x0c because P does not contain the value of the to be added to SUM, it contains the address where the value is to be found in the array. Source Code for Program 4: Adding Together the Elements of an Array. 0a 00 01 03 05 07 09 05 02 00 11 09 62 08 21 09 41 08 11 08 70 06 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 PC: START AC: 0 ARRAY: 1 3 5 7 LAST: 9 N: 5 P: ARRAY SUM: 0 START: LOAD DIRECT SUM ADD INDIRECT P STORE DIRECT SUM INCR DIRECT P LOAD DIRECT P SUB IMMEDIATE LAST char PC = START; char AC = 0; char ARRAY[] = {1, 3, 5, 7, 9}; char N = 5; char P = ARRAY; char SUM = 0; AC = SUM; AC += *P; SUM = AC; P++; AC = P; AC -= LAST; 17 b0 1a a0 0a c1 09 00 00 16 17 18 19 1a 1b 1c 1d END: BGZ IMMEDIATE END JMP START PRIND DIRECT SUM STOP 0 if (AC > LAST) goto END; goto START; cout << int(SUM); Dump for Program 4: 0 1 00 10 20 30 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 0a 00 01 03 05 07 09 05 02 00 11 09 62 08 21 09 | | 41 08 11 08 70 06 b0 1a a0 0a c1 09 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ Trace omitted .... 00 10 20 30 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 1d 01 01 03 05 07 09 05 07 19 11 09 62 08 21 09 | | 41 08 11 08 70 06 b0 1a a0 0a c1 09 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ Program 5: Computing the first ten Fibonacci numbers. The final example we discuss in this Section is a program which prints out the first 10 Fibonacci numbers. Recall that a Fibonacci number is defined by the recurrence relation x1 = 0 x2 = 1 xn = xn-1 + xn-2, when n>2. For example, if we have already computed x3 = 2, x4 = 3, and x5 = 5, then x6 = x5 + x4= 5+3 = 8. We can write a C program to compute the first 12 Fibonacci numbers as follows: 18 void main() { int a=0, b=1, c, n; for (n=10; n>0; n--) { c = a + b; a = b; b = c; cout << c << endl; } } The first 12 Fibonacci numbers are 0, 1, 1, 2, 3, 5, 8, 11, 19, 30, 49, 79. We initialize n to 10 because we don't print the first two numbers in the sequence. We can't calculate more terms even if we wanted to with 8 bit memory cells because the next number in the sequence is 128 which would cause an overflow. Source Code for Program 5: Compute the First 12 Fibonacci numbers. 06 00 00 01 00 0a 11 02 61 03 21 04 11 03 21 02 11 04 21 03 c1 04 d0 0a 51 05 11 05 b0 06 00 00 00 PC: START 01 AC: 0 02 A: 0 03 B: 1 04 C: 0 05 N: 10 06 START: LOAD 07 A 08 ADD 09 B 0a STORE 0b C 0c LOAD 0d B 0e STORE 0f A 10 LOAD 11 C 12 STORE 13 B 14 PRIND 15 C 16 PRINC 17 '\n' 18 DECR 19 N 1a LOAD 1b N 1c BGZ 1d START 1e STOP 1f 0 DIRECT char char char char char char AC = PC = START; AC = 0; A = 0; B = 1; C = 0; N = 10; A; DIRECT AC += B; DIRECT C = AC; DIRECT AC = B; DIRECT A = AC; DIRECT AC = C; DIRECT B = AC; DIRECT cout << int(C); IMMEDIATE cout << ‘\n’; DIRECT N--; DIRECT AC := N; IMMEDIATE if (AC>0) goto start; IMMEDIATE exit(1); Dump for Program 5: 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ 00 | 06 00 00 01 00 0a 11 02 61 03 21 04 11 03 21 02 | 10 | 11 04 21 03 c1 04 d0 0a 51 05 11 05 b0 06 00 00 | 20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 19 30 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ ...... Trace omitted 0 1 2 3 4 5 6 7 8 9 a b c d e f +-------------------------------------------------+ | 1f 00 37 59 59 00 11 02 61 03 21 04 11 03 21 02 | | 11 04 21 03 c1 04 d0 0a 51 05 11 05 b0 06 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +-------------------------------------------------+ 00 10 20 30 Exercises 1. Write MACHINE-16 source code to perform the following tasks. 2. Write a MACHINE-16 program which will read two decimal numbers at the keyboard (Instruction SCANF), add then, and print out the result. 3. Read a decimal number and print out its absolute value. 4. Read A and B in at the keyboard and print out A mod B which is computed by A - (A / B) * B The symbol “ /” represents integer division. 5. Compute the length of a line of characters entered at the keyboard. The line should be terminated by \n which is 0x0a. 6. Add up all numbers from 1 to 12 using a loop. 7. Compute terms of the recurrence relation until a negative result denoting overflow is encountered. x1 x2 x3 xn = = = = 0 3 1 xn-1 + 2xn-2 + xn-3 20