ENEE 350H: Computer Organization Project 1 (10%) Assigned: Tuesday, September 9 Due: Thursday, September 25 1. Purpose This project is intended to help you understand the instructions of a very simple assembly language and how to assemble programs into machine-level code. 2. Problem This project has three parts. In the first part, you will write a program to take an assembly- language program and produce the corresponding machine code. In the second part, you will write a behavioral simulator for arbitrary machine code. In the third part, you will demonstrate your assembler and simulator by writing a short assembly-language program to multiply two numbers. 3. LC-897 Instruction-Set Architecture For the first several projects, you will be gradually "building" the LC-897 (Little Com- puter, Fall 1997). The LC-897 is very simple, but it is general enough to solve complex problems. For this project, you will only need to know the instruction set and instruction format of the LC-897. The LC-897 is an 8-register, 16-bit computer. All addresses are shortword-addresses (i.e. address 0 corresponds to the first two bytes of main memory, address 1 corresponds to the second two bytes of main memory, etc.). By assembly-language convention, register 0 will always contain the value 0. The machine need not enforce this, but no assembly-lan- guage program should ever change register 0 from its initial value of 0. There are 4 instruction formats: R-type, I-type, J-type, and O-type. o R-type instructions (add, nand): bits 15-13: opcode bits 12-10: reg A bits 9-7: reg B bits 6-4: dest register bits 3-0: unused (should all be 0) o I-type instructions (lw, sw, beq): bits 15-13: opcode bits 12-10: reg A bits 9-7: reg B bits 6-0: immediate (a 7-bit, 2's complement number with a range of -64 to 63) o J-type instructions (jalr): bits 15-13: opcode bits 12-10: reg A bits 9-7: reg B bits 6-0: unused (should all be 0) o O-type instructions (halt, nop): bits 15-13: opcode bits 12-0: unused (should all be 0) The following table describes the different opcodes. Table 1: Description of Machine Instructions Mnemonic Name & Format Opcode Action add Add: R-type 000 Add contents of regA with regB, store result in dest. nand Nand: R-type 001 Nand contents of regA with regB, store results in dest. lw Load word: I-type 010 Load value from memory into regB. Memory address is formed by adding immediate with contents of regA. sw Store word: I-type 011 Store value from regB into memory. Memory address is formed by adding immediate with contents of regA. beq Branch if equal: 100 If the contents of regA and regB are I-type the same, branch to the address PC+1+immediate, where PC is the address of the beq instruction. jalr Jump-and-link-register: 101 First store PC+1 into regB, where PC J-type is the address of the jalr instruction. Then branch to the address now con- tained in regA. * Note that if regA is the same as regB, the processor will first store PC+1 into that register, then branch to it. halt Halt: O-type 110 Halt the machine; do nothing and announce that the machine has stopped. nop No-op: O-type 111 Do nothing for this cycle. 4. LC-897 Assembly Language and Assembler (3%) The first part of this project is to write a program to take an assembly-language program and translate it into machine-level code. You will translate assembly language names for instructions, such as beq, into their numeric equivalent (e.g. 100), and you will translate symbolic names for addresses into numeric values. The final output produced by the assembler will be a series of 16-bit instructions, written as ASCII text, hexadecimal for- mat, one line per instruction (i.e. each number separated by newlines). The format for a line of assembly code is: labelinstructionfield0field1field2comments The leftmost field on a line is the label field. Valid labels contain a maximum of 6 charac- ters and can consist of letters and numbers. The label is optional (the tab following the label field is not). Labels make it much easier to write assembly language programs, since otherwise you would need to modify all address fields each time you added a line to your assembly-language program! After the optional label is a tab. Then follows the instruction field, where the instruction can be any of the assembly-language instruction mnemonics listed in the above table. After another tab comes a series of fields. All fields are given as decimal numbers. The number of fields depends on the instruction. The following table describes each instruction. Format Meaning add dest regA regB R[dest] <- R[regA] + R[regB] nand dest regA regB R[dest] <- ~(R[regA] & R[regB]) lw regB regA immed R[regB] <- Mem[ R[regA] + immed ] sw regB regA immed R[regB] -> Mem[ R[regA] + immed ] beq regA regB immed if ( R[regA] == R[regB] ) { PC <- PC + 1 + immed (if label, PC <- label) } jalr regA regB PC <- R[regA] R[regB] <- PC + 1 halt halt + print state nop do nothing this cycle Note that even though the beq instruction shows the program counter being updated by the immediate value (PC <- label), the address represented by the label does not go into the instruction. The instruction format specifies that the immediate field must contain a PC-relative value. Therefore, if a label is used in the assembly program, the assembler must calculate the PC-relative value that will accomplish the same as a direct jump to that label. This amounts to solving the following equation for offset (label = PC + 1 + offset). Symbolic addresses refer to labels. For lw or sw instructions, the assembler should com- pute the immediate value to be equal to the address of the label. This could be used with a zero base register to refer to the label, or could be used with a non-zero base register to index into an array starting at the label. For beq instructions, the assembler should trans- late the label into the numeric immediate value needed to branch to that label. After the last used field comes another tab, then any comments. The comment field ends at the end of a line. Comments are vital to creating understandable assembly-language pro- grams, because the instructions themselves are rather cryptic. In addition to LC-897 instructions, an assembly-language program may contain directions for the assembler. The only assembler directive we will use is .fill (note the leading period). The .fill directive tells the assembler to put a number into the place where the instruction would normally be stored. The .fill directive uses one field, which can be either a numeric value or a symbolic address. For example, ".fill 32" puts the value 32 where the instruction would normally be stored. Using .fill with a symbolic address will store the address of the label. In the example below, ".fill start" will store the value 2, because the label "start" refers to address 2. The assembler should make two passes over the assembly-language program. In the first pass, it will calculate the address for every symbolic label. Assume that the first instruc- tion is at address 0. In the second pass, it will generate a machine-level instruction (in hexadecimal) for each line of assembly language. For example, here is an assembly-lan- guage program (that counts down from 5, stopping when it hits 0). lw 1 0 five load reg1 with 5 (uses symbolic address) lw 2 1 3 load reg2 with -1 (uses numeric address) start add 1 1 2 decrement reg1 beq 0 1 2 goto end of program when reg1==0 beq 0 0 start go back to the beginning of the loop nop done halt end of program five .fill 5 neg1 .fill -1 stAddr .fill start will contain the address of start (2) And here is the corresponding machine-level program: (address 0): 0x4087 (decimal 16519) (address 1): 0x4503 (decimal 17667) (address 2): 0x0510 (decimal 1296) (address 3): 0x8082 (decimal 32898) (address 4): 0x807d (decimal 32893) (address 5): 0xe000 (decimal 57344) (address 6): 0xc000 (decimal 49152) (address 7): 0x0005 (decimal 5) (address 8): 0xffff (decimal -1) (address 9): 0x0002 (decimal 2) Be sure you understand how the above assembly-language program got translated to this machine-code file. Since your programs will always start at address 0, your program should only output the contents, not the addresses. 4087 4503 0510 8082 807d e000 c000 0005 ffff 0002 4.1 Running Your Assembler You must write your program so that it is run as follows (assuming your program name is "assemble"). assemble assembly-code-file machine-code-file Note that the format for running the command must use command-line arguments for the file names (rather than standard input and standard output). The first argument is the file name where the assembly-language program is stored, and the second argument is the file name where the output (the machine-code) is written. Your program should only store the list of hexadecimal numbers in the machine-code file, one instruction per line-any other format will render your machine-code file ungradable. Each number can have `0x' in front or not, as you wish. Any other output that you want the program to generate (e.g. debugging output) can be printed to standard output. 4.2 Error Checking Your assembler should catch errors in the assembly language program, as well as errors that occur because the user ran your program incorrectly (e.g. with only 1 argument instead of 2 arguments). For example, it should detect the use of undefined labels, dupli- cate labels, missing arguments to opcodes (e.g. only giving two fields to lw), immediate values that are out of range, unrecognized opcode, etc. 4.3 Assembler Hints Printing out 16-bit numbers is done by attaching an `h' to the print-format string (`h' stands for half-word). For example, the following code prints out short integers correctly. int num = 0x983475; /* larger than a 16-bit quantity */ short hword; hword = num & 0xffff; printf("short int: 0x%04hx, %hd \n", hword, hword); The corresponding output: short int: 0x3475, 13429 The first number printed is the value of hword as a hexadecimal number (the `04' tells the printf function to pad the number on the left with zeroes if necessary, up to a total length of 4 digits). The second number printed is the value of hword as a decimal number. If you leave off the `h,' or instead use `l,' the value printed might not reflect the actual value of the number (heavily dependent on the compiler). Since the immediate value is a 2's complement number, it can only store numbers ranging from -64 to 63. For symbolic addresses, your assembler will compute the immediate value so that the instruction refers to the correct label. Remember to check the resulting immedi- ate value. Remember that the immediate value is only a 7-bit 2's complement number. Since Sun workstations are 32-bit or 64-bit machines, you'll have to chop off all but the lowest 7 bits for negative values. 5. Behavioral Simulator (4%) The second part of this assignment is to write a program that can simulate any legal LC- 897 machine-code program. The input for this part will be the machine-code file that you created with your assembler. With a program name of "simulate" and a machine-code file of "code", your program should be run as follows: simulate code > output This directs all printfs to the file "output". The simulator should begin by initializing all memory, registers, and the program counter to 0. The simulator will then simulate the program until the program executes a halt. After every instruction executed, the simulation should print the current state of the machine (program counter, registers, memory). Print the memory contents for memory locations defined in the machine-code file (addresses 0-9 in the Section 4 example). Print the final state of the machine at the end of the simulation, too. 5.1 Simulator Hints Since this is a 16-bit simulator, it might be a bit easier if every number in your simulator is of type short. You do not have to do this; it will simply save some debugging headache. Remember that whenever a variable is of type short, you must read into it (using scanf) or print it out (via printf) using a C printf format of `%hx' or `%hd' otherwise the printf/scanf function might read/write several extra bytes from/to surrounding data items. This is bad. Be careful how you handle the immediate values for lw, sw, and beq. Remember that they are 2's complement 7-bit numbers, so when you need to use immediate values in computa- tions, you will need to convert a negative value to a 16-bit or 32-bit negative number on the Sun workstation by sign extending it. To do this, use the following function (assuming you decide to use 16-bit short integers throughout; if you use 32-bit integers, replace each short with int). short convertNum(short num) { /* convert a 7-bit number into a 16-bit Sun number */ if (num & 0x40) { num -= 128; } return(num); } An example run of the simulator is included at the end of this handout. 6. Assembly-Language Multiplication (3%) Your simulator should work on any legal machine-code file, and your assembler should work on any legal assembly-language program. To show us how well your programs work, write an assembly-language program to multiply two numbers. Input the numbers by reading memory locations called "mcand" and "mplier". The result should be stored in register 1 when the program halts. You may assume that the two input numbers are at most 8 bits and 7 bits wide and that both are positive; this ensures that their (positive) result fits into an LC-897 word. See the algorithm in section 4.6 of the textbook for how to multiply. Remember that shifting left by one bit is the same as adding the number to itself. Given the LC-897 instruction set, it is easiest to modify the algorithm so that you avoid the right shift. Submit a version of the program that computes (123 * 229). You must use some kind of loop and shift algorithm to perform the multiplication-do not submit programs that compute the product via successive additions of one of the products (e.g. multiplying 5 * 6 by iteratively adding 5 six times) or perform each of the separate shift/add iterations with 8 different sections of code. Make sure your simulator and assembler work for all instructions, even if your demonstra- tion program doesn't contain any of a certain type of instruction. 7. Grading We will grade primarily on functionality, including error handling, correctly assembling and simulating all instructions, input and output format, method of executing your pro- gram, and correctly multiplying. Be very careful to follow exactly the format for inputting the assembly language program, outputting the machine-code file (from the assembler), and inputting the machine-code file (from the simulator). We will be running a solution assembler and simulator on your assembly-language and machine-code files, so they must be in exactly the right format. 8. Turning in the Project You should submit the following separate files: 1. C program listing for your assembler 2. C program listing for your simulator 3. Assembly file for the multiplication program 4. Machine-code file for the multiplication program To submit your programs and output files, use the submit350 program. This program will mail in any file you want to submit. For example, if your assembler C program is in assemble.c, your simulator is in simulate.c, your assembly code is in mult.assembly, and your machine-code is in mult.machine, execute the following commands: submit350 assemble.c submit350 simulate.c submit350 mult.assembly submit350 mult.machine You should receive an e-mail acknowledgment for each file you send in. Check to make sure the file you sent in is what you thought it was. Each of your C programs must be in a single file and must be able to be compiled by run- ning ccc. Do not expect us to specify additional flags or use a makefile. The official time of submission for your project will be the time the last file is sent. If you send in anything after the due date, your project will be considered late (and will use up your late days or will receive a zero). 9. Code Fragment for Assembler The focus of this class is machine organization, not C programming skills. To "build" your computer, however, you will be doing a lot of C programming. To help you, here is a frag- ment of the C program for the assembler. This shows how to specify command-line argu- ments to the program (via argc and argv), how to parse the assembly-language file, etc. This fragment is provided strictly to help you, though it may take a bit for you to under- stand and use the file. You may also choose to not use this fragment. /* Assembler code fragment for LC-897 */ #include #include #define MAXLINELENGTH 1000 char * readAndParse(FILE *, char *, char **, char **, char **, char **, char **); int isNumber(char *); main(int argc, char *argv[]) { char *inFileString, *outFileString; FILE *inFilePtr, *outFilePtr; char *label, *opcode, *arg0, *arg1, *arg2; char lineString[MAXLINELENGTH+1]; if (argc != 3) { printf("error: usage: %s \n", argv[0]); exit(1); } inFileString = argv[1]; outFileString = argv[2]; inFilePtr = fopen(inFileString, "r"); if (inFilePtr == NULL) { printf("error in opening %s\n", inFileString); exit(1); } outFilePtr = fopen(outFileString, "w"); if (outFilePtr == NULL) { printf("error in opening %s\n", outFileString); exit(1); } /* here is an example for how to use readAndParse to read a line from inFilePtr */ if (readAndParse(inFilePtr, lineString, &label, &opcode, &arg0, &arg1, &arg2) == NULL) { /* reached end of file */ } else { /* label is either NULL or it points to a null-terminated string in lineString that has the label. If label is NULL, that means the label field didn't exist. Same for opcode and argX. */ } /* this is how to rewind the file ptr so that you start reading from the beginning of the file */ rewind(inFilePtr); /* after doing a readAndParse, you may want to do the following to test each opcode */ if (!strcmp(opcode, "add")) { /* do whatever you need to do for opcode "add" */ } } char * readAndParse(FILE *inFilePtr, char *lineString, char **labelPtr, char **opcodePtr, char **arg0Ptr, char **arg1Ptr, char **arg2Ptr) { /* read and parse a line note that lineString must point to allocated memory, so that *labelPtr, *opcodePtr, and *argXPtr won't be pointing to readAndParse's memory also note that *labelPtr, *opcodePtr, and *argXPtr only point to memory in lineString. When lineString changes, so will *labelPtr, *opcodePtr, and *argXPtr returns NULL if at end-of-file */ char *statusString; statusString = fgets(lineString, MAXLINELENGTH, inFilePtr); if (statusString != NULL) { if (lineString[0] == `\t') { *labelPtr = NULL; *opcodePtr = strtok(lineString, "\t\n"); } else { *labelPtr = strtok(lineString, "\t\n"); *opcodePtr = strtok(NULL, "\t\n"); } *arg0Ptr = strtok(NULL, "\t\n"); *arg1Ptr = strtok(NULL, "\t\n"); *arg2Ptr = strtok(NULL, "\t\n"); } return(statusString); } int isNumber(char *string) { /* return 1 if string is a number */ int i; return( (sscanf(string, "%d", &i)) == 1); } 10. Code Fragment for Simulator Here is some C code that may help you write the simulator. Again, you should take this merely as a hint. You may have to re-code this to make it do exactly what you want, but this should help you get started. /* instruction-level simulator for LC-897 */ #include #include #define MAXLINELENGTH 1000 #define MEMSIZE 0x7fff /* maximum number of memory words */ short MainMemory[ MEMSIZE ]; main(int argc, char *argv[]) { FILE *filePtr; short address; char line[MAXLINELENGTH]; if (argc != 2) { printf("error: usage: %s \n", argv[0]); exit(1); } filePtr = fopen(argv[1], "r"); if (filePtr == NULL) { printf("error: can't open file %s", argv[1]); perror("fopen"); exit(1); } /* read in the entire machine-code file into memory */ for ( address = 0; fgets(line, MAXLINELENGTH, filePtr) != NULL; address++) { if (sscanf(line, "%hx", &MainMemory[address]) != 1) { printf("error in reading address %d\n", address); exit(1); } printf("memory[%hd]=%hx\n", address, MainMemory[address]); } } 11. C Programming Tips Here are a few programming tips for writing C programs to manipulate bits: 1. To indicate a hexadecimal constant in C, precede the number by 0x. For example, 27 decimal is 0x1b in hexadecimal. 2. The value of the expression (a >> b) is the number a shifted right by b bits. Neither a nor b are changed. E.g. (25 >> 2) is 6. Note that 25 is 11001 in binary, and 6 is 110 in binary. 3. The value of the expression (a << b) is the number a shifted left by b bits. Neither a nor b are changed. E.g. (25 << 2) is 100. Note that 25 is 11001 in binary, and 100 is 1100100 in binary. 4. The value of the expression (a & b) is a logical AND on each bit of a and b (i.e. bit 15 of a ANDed with bit 15 of b, bit 14 of a ANDed with bit 14 of b, etc.). E.g. (25 & 11) is 9, since: 11001 (binary) & 01011 (binary) ----------------- = 01001 (binary), which is 9 decimal. 5. The value of the expression (a | b) is a logical OR on each bit of a and b (i.e. bit 15 of a ORed with bit 15 of b, bit 14 of a ORed with bit 14 of b, etc.). E.g. (25 | 11) is 27, since: 11001 (binary) | 01011 (binary) ----------------- = 11011 (binary), which is 27 decimal. 6. ~a is the bit-wise complement of a (a is not changed); if a = 100101, ~a = 011010. Use these operations to create and manipulate machine-code. E.g. to look at bit 3 of the variable a, you might do: (a>>3) & 0x1. To look at bits 15-13 of a 16-bit word (for instance, the opcode of each instruction), you could do: (a>>13) & 0x7. To put a 6 into bits 5-3 and a 3 into bits 2-1, you could do: (6<<3) | (3<<1). If you're not sure what an operation is doing, print some intermediate results to help you debug. 12. Example run of simulator The following is example output of an LC-897 simulator (160 lines of C code). This example run simulates the original example LC-897 machine code: lw 1 0 five load reg1 with 5 (uses symbolic address) lw 2 1 3 load reg2 with -1 (uses numeric address) start add 1 2 1 decrement reg1 beq 0 1 2 goto end of program when reg1==0 beq 0 0 start go back to the beginning of the loop nop done halt end of program five .fill 5 neg1 .fill -1 stAddr .fill start will contain the address of start (2) Note that you do not have to follow this format for the output, but you do have to present this information (after every cycle, print contents of memory, program counter, and regis- ter file). Print out the contents of memory in hexadecimal (since much of it will be instruc- tion words), and print out the contents of the register file in decimal (since much of it will be data words and memory addresses). memory[0]=4087 memory[1]=4503 memory[2]=510 memory[3]=8082 memory[4]=807d memory[5]=e000 memory[6]=c000 memory[7]=5 memory[8]=ffff memory[9]=2 state after cycle 0: pc=1 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 5 reg[2] 0 reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 1: pc=2 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 5 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 2: pc=3 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 4 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 3: pc=4 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 4 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 4: pc=2 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 4 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 5: pc=3 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 3 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 6: pc=4 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 3 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 7: pc=2 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 3 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 8: pc=3 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 2 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 9: pc=4 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 2 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 10: pc=2 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 2 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 11: pc=3 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 1 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 12: pc=4 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 1 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 13: pc=2 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 1 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 14: pc=3 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 0 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 state after cycle 15: pc=6 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 0 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0 machine halted total of 17 instructions executed state after cycle 16: pc=7 memory: mem[0] 4087 mem[1] 4503 mem[2] 510 mem[3] 8082 mem[4] 807d mem[5] e000 mem[6] c000 mem[7] 5 mem[8] ffff mem[9] 2 registers: reg[0] 0 reg[1] 0 reg[2] ffff reg[3] 0 reg[4] 0 reg[5] 0 reg[6] 0 reg[7] 0