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 ([email protected]). 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