Project Overview
Transcription
Project Overview
PIMPF — The IFMP Processor Graded Programming Project, VL Informatik (252-0847-00)∗ Submission Deadline: November 18, 2014 Abstract In this project, you will write a C++ program for simulating a fully-fledged virtual CPU (central processing unit, or simply processor) that is able to execute programs in machine language. We call this virtual processor PIMPF, an acronym of IFMP processor. You will understand how a processor works, what machine language is, and how machine language can be displayed in human readable assembler format. For testing your simulation, you obtain five example programs and an autograder that automatically checks whether your simulation is correct on these five programs. If you hand in your solution, it will automatically be checked (and graded), based on its correctness on a larger set of programs not known to you. The result will provide important feedback concerning your current understanding of the course material and your programming skills at this point in time. The project also features a programming contest: who can write the shortest PIMPF program that performs a certain task? The best contestant (team, or individual student) will get a prize. Although the long and technical descriptions in parts of this project may look frightening at first, be assured that it is not as difficult as it may appear. We have subdivided the project into five steps, and the autograder can be used to check the correctness of each individual step. We believe that this project is fun and hope you enjoy working on it! Part I Project Overview 1 What is a CPU? The CPU (central processing unit, or processor) is the “heart” of every computer, and it executes programs in machine language. ∗ The grade obtained in this project does not count towards your final grade, but provides important feedback. 1 The CPU has access to memory whose cells are able to store program instructions and data; this is the von Neumann architecture. In addition, there is a program counter, storing the memory address of the next instruction to be executed. A run of the program starts with the program counter set to the address of the first instruction. Then the CPU repeatedly performs the following cycle: 1. Fetch the instruction at the address stored in the program counter (you can think of an instruction as an unsigned integer); 2. Decode the instruction into the actual command and its operands; 3. Execute the decoded instruction. This may change the content of memory cells, read input, or write output. It will also change the program counter. The program run stops after a halt instruction has been executed. The following figure gives a visual summary of a cycle. read Fetch read Decode write Execute read / write Memory Program Counter Repeat until halted: Computation Device Example. After loading the test program Adder.cpu, the memory attached to PIMPF and the program counter (pc) look as shown in the left table. Values starting with 0x are hexadecimal, and a blank indicates the initial value 0xffffffff of each cell before the program is loaded. In the right table, the fetched instruction is shown, along with its decoding into four pairs of hex nibbles, the resulting command and its operands, and the effect of executing the instruction. We now go through all five cycles of the program. pc → address 0 1 2 3 4 5 6 7 8 9 10 11 ··· value 0x060a0200 0x060b0300 0x010a0b0a 0x210a0000 0x30000000 0 Cycle 0: fetched instruction decoded instruction command and operands effect of execution ··· 2 0x060a0200 06 0a 02 00 set 10, 2, 0 store value 2 at address 10 and increase pc by 1 pc → pc → pc → address 0 1 2 3 4 5 6 7 8 9 10 11 ··· value 0x060a0200 0x060b0300 0x010a0b0a 0x210a0000 0x30000000 0 address 0 1 2 3 4 5 6 7 8 9 10 11 ··· value 0x060a0200 0x060b0300 0x010a0b0a 0x210a0000 0x30000000 0 address 0 1 2 3 4 5 6 7 8 9 10 11 ··· value 0x060a0200 0x060b0300 0x010a0b0a 0x210a0000 0x30000000 0 Cycle 1: fetched instruction decoded instruction command and operands effect of execution 0x060b0300 06 0b 03 00 set 11, 3, 0 store value 3 at address 11 and increase pc by 1 2 ··· Cycle 2: fetched instruction decoded instruction command and operands effect of execution 0x010a0b0a 01 0a 0b 0a add 10, 11, 10 add values at addresses 10 and 11, store the result at address 10, and increase pc by 1 2 3 ··· Cycle 3: fetched instruction decoded instruction command and operands effect of execution program output 5 3 ··· 3 0x210a0000 21 0a 00 00 out 10 output value at address 10 and increase pc by 1 5 pc → address 0 1 2 3 4 5 6 7 8 9 10 11 ··· value 0x060a0200 0x060b0300 0x010a0b0a 0x210a0000 0x30000000 0 Cycle 4: fetched instruction decoded instruction command and operands effect of execution 0x30000000 30 00 00 00 hlt stop the program 5 3 ··· Hence, Adder.cpu is a program that adds 2 and 3, and outputs the result 5. Not very exciting, but believe us that some of the other test programs are more interesting. 2 PIMPF Your task in this project is to implement a C++ program that simulates a particular CPU called PIMPF. This processor has access to 1 KB of memory, and it supports 16 different commands, four of which we have already seen in the example above. The execution of the instructions will have to comply exactly with the technical specification of PIMPF in Part II below. On the way, you will write a disassembler for displaying machine language instructions in a human readable format, so that you can actually understand the programs that PIMPF is running. For example, the program Adder.cpu reads as 060a0200 060b0300 010a0b0a 210a0000 30000000 0 but after disassembly, it will display as asm> asm> asm> asm> asm> set set add out hlt 10, 2, 0 11, 3, 0 10, 11, 10 10 Despite its simplicity, PIMPF is as powerful as any processor in a real computer. Within the memory limits, everything that can be done in C++ is also possible with PIMPF. For example, one of the test programs that we provide enumerates prime numbers. In fact, you can write your own PIMPF programs in the human readable asm format and then use the assembler that we provide to turn them into .cpu files that PIMPF understands. We hope that this is yet another motivation for you to get started with PIMPF! Important: Your solution is graded automatically. It is therefore important to exactly follow the implementation and submission guidelines below, otherwise the autograder may fail to correctly process your solution. 4 3 Material Provided On the course homepage, you find the library pimpf_lib.cpp that contains the memory accessed by PIMPF. Moreover, pimpf_lib.cpp contains functionality to automatically check your implementation. Of course you are encouraged to look into the library. But do not modify it! Otherwise the autograder may produce wrong results. Place the library and your program in the same directory, and include the library in your solution program pimpf.cpp, using #include "pimpf_lib.cpp". The following test programs are provided by us as a series of hexadecimal integers, also available on the course homepage. 1. Output: Outputs the value 42. (Output.cpu: 60a2a00 210a0000 30000000 0) 2. Adder: Adds 2 and 3 and outputs the result 5. (Adder.cpu: 60a0200 60b0300 10a0b0a 210a0000 30000000 0) 3. Loop: Counts backward from 10 to 1 and outputs each number. (Loop.cpu: 060a0a00 060b0000 060c0100 15070a0b 210a0000 020a0c0a 10030000 30000000 0) 4. Gcd: Calculates the greatest common divisor of 1071 and 1029 and outputs the result 21. An adaptation (Gcd with input) for computing the greatest common divisor of 1071 and 1029, provided as input numbers, is also available. (Gcd.cpu: 60a2f04 60b0504 60c0000 15080b0c 50a0b0d 10b0c0a 10d0c0b 10030000 210a0000 30000000 0) 5. Primes: Calculates all prime numbers up to 255. The program is easily adaptable to computing all prime numbers up to a given input number. (Primes.cpu: 6120000 6130100 611ff00 6140200 6150100 1151315 150d1415 5141516 150a1612 10050000 1141314 150f1411 10040000 21140000 100a0000 30000000 0) We also provide the following “non-test” program that allows you to see what the autograder is doing in this case (you’ll need this in the programming contest below): 6. Loopf: Counts forward from 0 to 9 and outputs each number. (Loopf.cpu: 60a0000 60b0a00 60c0100 15070a0b 210a0000 10a0c0a 10030000 30000000 0) The ”Technical Specification of PIMPF” (from which you can find out what the hexadecimal instructions mean) can be found in Part II. Finally, the course homepage also contains a program pimpf_assembler.cpp that translates human readable programs in asm format into the cpu format understood by PIMPF. This for example makes it easy to perform the mentioned adaptation of Primes.cpu to an arbitrary input number, and it also supports you in creating your own (test) programs, notably for the programming contest. 5 4 The Five Steps The implementation of PIMPF will be accomplished in five steps as outlined in the rough solution below. In the spirit of stepwise refinement, we recommend to replace the comments in the rough solution with function calls first, and then provide the function definitions! You must proceed step by step, starting with step (a), in order for the autograder to produce correct results! // Prog : p i m p f . cpp // s i m u l a t e s t h e v i r t u a l p r o c e s s o r PIMPF #i n c l u d e <i o s t r e a m > #i n c l u d e ” p i m p f l i b . cpp ” i n t main ( ) { // ( a ) l o a d program d a t a i n t o PIMPF ’ s memory // ( b ) p r o v i d e f u n c t i o n a l i t y t o d e c o d e i n s t r u c t i o n s // ( c ) p r o v i d e f u n c t i o n a l i t y t o d i s a s s e m b l e and // p r i n t i n s t r u c t i o n s i n human r e a d a b l e f o r m a t // ( d ) e x e c u t e i n s t r u c t i o n s return 0; } // ( e ) t h e c o n t e s t : f i n d and r u n ( w i t h p i m p f . cpp ) a s h o r t e s t program // t h a t s e t s a l l t h e memory c e l l s b e h i n d t h e program i t s e l f t o 0 Provided that you have included pimpf_lib.cpp in your program, the program will always show the results of each step, with comments by the autograder beginning with the following text: -----------------------------------------------------------The following output has been generated by PIMPF autograder. -----------------------------------------------------------Note: Parts of step (b) will be reused in steps (c) and (d), but for the sake of grading you are required to implement the five steps individually, in the order (a)—(e). (a) Loading the Program – 1 Point A PIMPF program is provided in cpu format as a sequence of hexadecimal1 numbers. In order to load a program into the memory accessed by PIMPF, the sequence of numbers has to be read from standard input and written to memory. The PIMPF memory is described in detail in Section 6 of the PIMPF Technical Specification. 1 We have deliberately chosen this format in order to make a decoding of the instructions easily possible for a human. 6 Task: Read the values from standard input and write them to memory in consecutive order starting at memory address 0. A zero indicates end of data and must also be written to memory. Reading an unsigned int variable i in hexadecimal form from standard input can be accomplished with std::cin >> std::hex >> i. Once switched to hexadecimal mode, the input can be switched back to decimal mode with std::cin >> std::dec. To write a value to a specific address in memory, use the following function from pimpf_lib.cpp: void pimpf::write_to_memory (unsigned int address, unsigned int value); Verification: Use one of the test programs as input, such as the program Gcd.cpu: 60a2f04 60b0504 60c0000 15080b0c 50a0b0d 10b0c0a 10d0c0b 10030000 210a0000 30000000 0 In a Linux console, you can simply type ./pimpf < Gcd.cpu to make your program read its input from the file Gcd.cpu. Alternatively, you can just call ./pimpf (the program waits for input), then copy and paste the content of Gcd.cpu to the console and press enter. If your program correctly reads and stores the numbers to memory, and if you use one of the provided test programs, the autograder will indicate success with Detected program: <name> Part (a) passed Only proceed with step (b) when you see success for step (a) with all five test programs. Programs with input. A program that expects user input can be read as above from a file, if the file already contains the respective inputs in the end (after the value 0 that signals the end of the program). For example, your final program will be able to run ./pimpf < Gcd_with_input.cpu, with the same result as ./pimpf < Gcd.cpu, because the file Gcd_with_input.cpu contains the two numbers 1071 and 1029 in the end. Alternatively, calling ./pimpf and pasting the content of Gcd_waiting_for_input.cpu into the console makes the simulator wait for the two input numbers. (b) Decoding Instructions – 2 Points Instructions stored in memory must be decoded. Decoding means to split the 32-bit value read from memory into four 8-bit components, containing the instruction’s opcode and three operands, some of which may be irrelevant for the given opcode; see Section 7 of the PIMPF Technical Specification. Task: Process all instructions in memory starting from memory address 0 until you read a value of zero. For each instruction (excluding the trailing zero), decode it into its components opcode, op1, op2 and op3 and call the following function from pimpf_lib.cpp: void pimpf::print_decoded_instruction (unsigned int opcode, unsigned int op1, unsigned int op2, unsigned int op3); This will record your decoding for output and verification. We remark that decoding can be achieved with (integer) division and modulus. 7 Verification: If you use program Gcd.cpu as input, the following output should appear: Your decoding: -------------dec> opcode= 6, ops= 10, 47, 4 dec> opcode= 6, ops= 11, 5, 4 dec> opcode= 6, ops= 12, 0, 0 dec> opcode= 21, ops= 8, 11, 12 dec> opcode= 5, ops= 10, 11, 13 dec> opcode= 1, ops= 11, 12, 10 dec> opcode= 1, ops= 13, 12, 11 dec> opcode= 16, ops= 3, 0, 0 dec> opcode= 33, ops= 10, 0, 0 dec> opcode= 48, ops= 0, 0, 0 If your program correctly decodes instructions, and you use one of the test programs as input, the autograder should indicate success for (b) with ”Step (b) passed”. Only proceed with step (c) when you see success for (b) with all five test programs. (c) Disassembly – 3 Points An opcode uniquely identifies an command. The meaning of the operands varies between different commands. Disassembly is the translation of (opcode, op1, op2, op3) into a human-readable format. The reverse is called assembly. In order to execute instructions on PIMPF, decoding them is enough; however, if you want to understand what a program such as Gcd.cpu is really doing, the opcodes are not telling you much. Disassembling them leads to a more readable version of the machine language code. For example, dec> opcode= 6, ops= 10, 47, 4 yields asm> set 10, 47, 4 which tells us that this instruction stores the value 47 + 4 · 256 = 1071 at address 10. Valid opcodes along with their readable names (mnemonics) and operands are summarized in Section 8 of the PIMPF Technical Specification. Do not yet think about the meaning of the instructions. It is the sole purpose of this task to transform instructions from one format into another. Figure 1 on Page 10 illustrates the disassembly step for the program Gcd.cpu. 8 Task: Process all instructions in memory, starting from address 0, until you read a value of zero. Decode each instruction (without calling pimpf::print_decoded_instruction again) into its components opcode, op1, op2 and op3; then disassemble the instruction and output the result to the following stream from pimpf_lib.cpp: pimpf::disassembly. This stream can be used in exactly the same way as std::cout; it records your disassembly for output and verification. A disassembled instruction always has the format <name> <operand list> "\n" where <name> is to be replaced by the name (mnemonic) of the opcode, such as add or jmp. For illegal opcodes, i.e. opcodes that do not appear in the instruction set, use the word illegal as mnemonic. The <operand list> is either empty (in case of hlt), consists of one unsigned integer operand (in case of jmp, in and out), or of exactly three comma separated unsigned integer operands that should be output in decimal format. Put spaces between the name and the operand list and a new line ("\n") after each instruction. Verification: If you use the test program Gcd.cpu as input, the following output should appear: Your disassembly [and my assembly of it]: ------------------------------------------------asm> set 10, 47, 4 [ 60a2f04] asm> set 11, 5, 4 [ 60b0504] asm> set 12, 0, 0 [ 60c0000] asm> jeq 8, 11, 12 [15080b0c] asm> mod 10, 11, 13 [ 50a0b0d] asm> add 11, 12, 10 [ 10b0c0a] asm> add 13, 12, 11 [ 10d0c0b] asm> jmp 3 [10030000] asm> out 10 [210a0000] asm> hlt [30000000] Note that the autograder already tries to assemble your disassembly and therefore helps in order to check the correctness of your output. Compare the hexadecimal numbers enclosed in the brackets with the input file Gcd.cpu! If you use one of the test programs as input, the autograder should indicate success for (c) with ”Step (c) passed”. Only proceed with step (d) when you see success for step (c) with all test programs. (d) Execution – 5 Points This is the most difficult step. You have understood how to decode and disassemble instructions. Now you have to execute instructions in cycles, for which you need to understand the details of a PIMPF cycle (Section 9), and the details of the PIMPF instruction set (Section 10). Once you have done this, you can fully understand and execute a program such as our test program Gcd.cpu that we display in Figure 1. 9 Hexadecimal instructions 0 : 60 a 2 f 0 4 1 : 60 b0504 2 : 60 c0000 3 : 15080 b0c 4 : 50 a0b0d 5 : 10 b0c0a 6 : 10 d0c0b 7 : 10030000 8 : 210 a0000 9 : 30000000 set set set jeq mod add add jmp out hlt Disassembled instructions (plus comments) 10 , 47 , 4 // a = 1071 11 , 5 , 4 // b = 1029 12 , 0 , 0 // c o n s t a n t c = 0 8 , 1 1 , 12 // i f b = 0 g o t o 8 1 0 , 1 1 , 13 // h = a mod b 1 1 , 1 2 , 10 // a = b 1 3 , 1 2 , 11 // b = h 3 // g o t o 3 10 // p r i n t a // s t o p Figure 1: The test program Gcd.cpu and its disassembly Gcd.asm Task: Fetch, decode and execute instructions in memory starting at memory address 0 (the initial value of a program counter variable that you need to maintain). The next instruction to be executed according to the program counter can be accessed using the function pimpf::read_from_memory. Stop execution when a hlt instruction is encountered. For an illegal instruction, your simulation should stop with an assertion. The output of an out instruction has to be performed using the following function from pimpf_lib.cpp: void print_program_output (unsigned int value); The input of an in instruction must be read from std::cin and written to memory using pimpf::write_to_memory. Effects of other instructions can be realized by using the function pimpf::write_to_memory, and by changing the program counter. We suggest to implement the instruction execution in the following order: 1. General Instructions. You can test the instructions using the test programs Output.cpu and Gcd_with_input.cpu. 2. Arithmetic instructions. You can test some of these instructions using the test programs Adder.cpu, Loop.cpu, Gcd.cpu, and Primes.cpu 3. Branch instructions. You can test some of these instructions using the test programs Loop.cpu, Gcd.cpu, and Primes.cpu. Verification: If you use the test program Gcd.cpu as input, the following output should appear: Your program output: -------------------out> 21 10 If you use one of the test programs as input, the autograder should indicate success for (d) with "Step (d) passed". If you see all steps passed for all test programs, it is quite possible that you did everything right. However, check your program well (for example, with your own test programs), because we will also test your simulator with more programs and provide grades according to what kind of test results we observe. If you want to be really careful, make sure that each of the 16 PIMPF commands appears in at least one of your test programs. This testing approach is called code coverage. Your program pimpf.cpp is now complete. For the final step, you will not have to change it anymore. (e) Programming Contest This step has no points assigned to it, but the shortest solution will receive a prize. In this step, you need to write your own machine language program Memory_sweep.cpu. To facilitate this task, you may use the provided program pimpf_assembler.cpp that allows you to write the program in readable asm format (even with comments), and then automatically assemble it into cpu format. As an example, consider the file Loopf.asm: set set set jeq out add jmp hlt 10, 0, 0 11, 10, 0 12, 1, 0 7, 10, 11 10 10, 12, 10 3 // // // // // // // // 0: 1: 2: 3: 4: 5: 6: 7: i = 0 constant 10 constant 1 if i == 10 goto 7 output i i = i + 1 goto 3 stop Calling ./pimpf_assembler < Loopf.asm > Loopf.cpu will produce the file Loopf.cpu, our nontest file. This can then be run via ./pimpf < Loopf.cpu. Task: Write a PIMPF program Memory_sweep.cpu that stores the value 0 at all memory cells behind the program. More precisely, if your program has n instructions (excluding the trailing 0), it is being loaded to the addresses 0, 1, . . . , n − 1. After running your program, the memory cells at the addresses n, n + 1, . . . , 255 should all hold the value 0. Note that you actually need to do something here, since the initial value of an unused memory cell is 0xffffffff. The simplest way to fulfill this task is to write any program with n = 255 instructions. This will be stored in memory at the addresses 0, 1, . . . , 254, and with the trailing 0 at address 255, you are already done. The question is: can you do it with less instructions, and if so, how many do you need? Contest: find the shortest program you can (in terms of the number n of instructions) that performs the above memory sweep task! The authors of the shortest program(s) will receive a prize! Verification: If you use a program as input that is not one of the test programs, for example Loopf.cpu, the following output should appear (a memory dump, listing the contents of all 256 memory cells in order): 11 Memory after your program execution: -----------------------------------[ 60a0000][ 60b0a00][ 60c0100][15070a0b][210a0000][ 10a0c0a][10030000][30000000] [ 0][ffffffff][ a][ a][ 1][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] [ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff][ffffffff] Number of program instructions: 8 The memory dump lets you see whether the memory cells behind your program were indeed swept (i.e. filled with 0). If you use a correct program Memory_sweep.cpu as input, the autograder should indicate success for step (e) with "Step (e) passed". 5 Submission Guidelines You are invited to work on the project on your own, or in teams. In the latter case, you can either select a team leader responsible for submitting the solution, or each team member individually submits 12 a solution. We will autograde all solutions that adhere to the submission guidelines below, and we will post obtained grades by Legi number on the course webpage. We will also post the Legi numbers that receive prizes. For individual feedback, you are welcome to contact your exercise class assistant. Task: Submit your solutions to chief assistant Christian Zingg (zinggch@student.ethz.ch). The solution for steps (a)–(d) has to be provided as a single C++ source file named pimpf_<legi>.cpp, where <legi> is your Legi number. Example: pimpf_01-123-523.cpp. Also write the full names of all students involved as a comment in the beginning of the file. The solution to the programming contest in step (e) has to be provided as a single cpu file named Memory_sweep_<legi>.cpu where <legi> is your Legi number. Example: Memory_sweep_01-123-523.cpu. You can also participate in the programming contest if you do not submit a solution for steps (a)–(d). But without a solution for steps (a)–(d), it can be difficult to test your solution for step (e). Before submitting your programs, please check that: 1. Your C++ program is fully contained in the pimpf_<legi>.cpp source file; 2. Your C++ program compiles and works using the provided unmodified pimpf lib.cpp library; 3. You use only the functions pimpf::write_to_memory and pimpf::read_from_memory to access PIMPF’s memory. 4. You use only the function pimpf::print_decoded_instruction to print decoded instructions; 5. You use only the stream pimpf::disassembly to print disassembled instructions; 6. You use only the function pimpf::print_program_output to print output of PIMPF’s out instruction; 7. You do not include any non-standard libraries; 8. Your C++ program passes the steps (a)–(d) on all test programs; 9. Your C++ program passes step (e) on your cpu program. The verification and grading of your solution is done automatically. If any of the first 7 conditions are not met, your program will not be graded. This means that you will not get valuable feedback for your solution. If condition 8 is not met, it is very likely that your solution receives a lower number of points. If condition 9 is not met (for example, because you don’t submit a cpu program), you are not eligible for a prize. 13 Part II Technical Specification of PIMPF 6 Memory Instructions and data of PIMPF are stored in memory. The provided memory of our machine has a total capacity of 1 KB. Every memory cell can store one 32-bit unsigned integer. Thus there are 256 different addresses (from 0 to 255). 0 1 2 3 ... ... ... 252 253 254 255 The functionality that implements this memory is provided by the pimpf_lib.cpp library and must be used in your program. Memory access is provided by the following functions: • void pimpf::write_to_memory (unsigned int address, unsigned int value); This function stores the value at the given address. • unsigned int pimpf::read_from_memory (unsigned int address); This function returns the value at the given address. In both cases, valid addresses are in {0, 1, . . . , 255}. 7 Instruction Format A 32-bit instruction is made up of the following components with 8 bit each: Opcode, operand 1, operand 2, and operand 3. This is visualized in the following schema: Operation Code (opcode, 8 bit) 31, most significant bit Operand 1 (op1, 8 bit) Operand 2 (op2, 8 bit) Operand 3 (op3, 8 bit) least significant bit, 0 An opcode uniquely identifies a command. The meaning of the operands varies between different commands. 8 Instruction Set Overview An instruction consists of an opcode and operands. An opcode can be considered as a function name (the command) while operands correspond to function arguments. An instruction set consists of all instructions that a processor can understand. The following table lists the instruction set of our machine by opcode. 14 opcode 0x01 0x02 0x03 0x04 0x05 0x06 0x10 0x11 name add sub mul div mod set jmp jge used op1, op1, op1, op1, op1, op1, op1 op1, operands op2, op3 op2, op3 op2, op3 op2, op3 op2, op3 op2, op3 opcode 0x12 0x13 0x14 0x15 0x16 0x21 0x22 0x30 op2, op3 name jle jgr jls jeq jne out in hlt used op1, op1, op1, op1, op1, op1 op1 operands op2, op3 op2, op3 op2, op3 op2, op3 op2, op3 With the knowledge of this table, it is possible to disassemble an instruction. 9 The Cycle 1. Fetch. The next instruction is fetched. The program counter contains the memory address where this instruction is to be found. Upon start-up of PIMPF, the program counter must be set to the first instruction of the loaded program. This is always the one at memory address 0. 2. Decode. The instruction fetched in the previous phase is decoded to its components opcode, operand 1, operand 2, and operand 3. The opcode uniquely identifies a command. 3. Execute. Each instruction has effects. Execution of an instruction means to apply these effects. In our processor, the effect can be a modification of memory content, an input, or an output. Moreover, each instruction modifies the program counter: with the execution of an instruction, the program counter is either set to the next instruction in memory, or—in case of branch instructions—to the branch target if the branch is taken. 10 Instructions In the following precise specification of PIMPF’s instruction set, mem[a] stands for the memory value at address a. The program counter is abbreviated as pc. There are three categories of instructions. 1. General instructions (assignment, input, output, program halt); 2. Arithmetic instructions (addition, subtraction, multiplication, (integer) division and modulus); 3. Branch instructions (unconditional jump, conditional jumps based on comparing values at two memory cells). 15 10.1 General Instructions Name set op1,op2,op3 Opcode 0x06 Description Writes the (unsigned int) value op2 + op3 * 256 to memory address op1. The value has at most 16 significant bits. Effect: mem[op1] = op2 + op3 * 256 pc = pc + 1 out op1 0x21 Prints the value stored in mem[op1]. Effect: Outputs mem[op1] via std::cout pc = pc + 1 Important: Your simulator has to make all outputs using the function pimpf::print program output (unsigned int value); in op1 0x22 Reads a value and stores it in mem[op1]. Effect: Inputs an unsigned integer value from std::cin in decimal format 2 and stores it in mem[op1] pc = pc + 1 Important: Your simulator has to read the value from std::cin (without prompting the user) and store it in memory using the function pimpf::write_to_memory. hlt 0x30 Halts the program. Effect: Stops the program and therefore also has to stop your simulator. 2 Do not forget to switch the input stream back to decimal format with std::cin >> std::dec if it has been switched to hexadecimal format before. 16 10.2 Arithmetic instructions Arithmetic instructions implement common arithmetic operations (+, −, ∗, /, %). They all work on 32-bit unsigned integers. Then behavior of the operations corresponds exactly to the behavior of the corresponding C++ operators. In particular, when the result of a subtraction is negative, we obtain the result as a positive unsigned integer in two’s complement. Name add op1,op2,op3 Opcode 0x01 Description Adds the two values mem[op1] and mem[op2] and stores the result in mem[op3]. Effect: mem[op3] = mem[op1] + mem[op2] pc = pc + 1 sub op1,op2,op3 0x02 Subtracts value mem[op2] from value mem[op1] and stores the result in mem[op3]. Effect: mem[op3] = mem[op1] - mem[op2] pc = pc + 1 mul op1,op2,op3 0x03 Multiplies the two values mem[op1] and mem[op2] and stores the result in mem[op3]. Effect: mem[op3] = mem[op1] * mem[op2] pc = pc + 1 div op1,op2,op3 0x04 Divides value mem[op1] by value mem[op2] and stores the result in mem[op3]. Effect: mem[op3] = mem[op1] / mem[op2] (integer division!) pc = pc + 1 mod op1,op2,op3 0x05 Calculates value mem[op1] modulo value mem[op2] and stores the result in mem[op3]. Effect: mem[op3] = mem[op1] % mem[op2] pc = pc + 1 17 10.3 Branch instructions Branch instructions tell PIMPF to continue execution at an instruction different from the next one in the program. There is an unconditional jump, and six different jump commands that are triggered by a condition. Name jmp op1 Opcode 0x10 Description Continues execution at memory address op1. Effect: pc = op1 jeq op1,op2,op3 0x15 Jump if equal Effect: If mem[op2] == mem[op3] then pc = op1 else pc = pc + 1 jne op1,op2,op3 0x16 Jump if not equal Effect: If mem[op2] != mem[op3] then pc = op1 else pc = pc + 1 jgr op1,op2,op3 0x13 Jump if greater Effect: If mem[op2] > mem[op3] then pc = op1 else pc = pc + 1 jls op1,op2,op3 0x14 Jump if less Effect: If mem[op2] < mem[op3] then pc = op1 else pc = pc + 1 jge op1,op2,op3 0x11 Jump if greater or equal Effect: If mem[op2] >= mem[op3] then pc = op1 else pc = pc + 1 jle op1,op2,op3 0x12 Jump if less or equal Effect: If mem[op2] <= mem[op3] then pc = op1 else pc = pc + 1 18