SimmySpecifficationVersion1 - MIPT-ILab/MDSP GitHub Wiki
This page introduces the first version of specification for Simmy, an educational simulator for SIM computer architecture. SIM stands for Simple Integer Machine. It is not an existing architecture, but it implements all significant features of real computer architectures. Its description will be presented step by step. Each new version will add new commands and requirements and also change previous ones.
This version provides only an initial information about SIM. A very limited, but still operable, set of instructions is introduced and also some helpful tips and requirement to develop the functional simulator (the first version of Simmy) are presented in the following sections.
.
SIM architecture supports 16 general purpose integer registers. Each registers contains 16 bits for storing an unsigned value and 1 bit for a sign. If the sign bit is equal to 0 the register store a negative value, otherwise, a positive ones.
The following figure represents the logical structure of the register file:
register
number sign unsigned value
┌―――┐ ┌――――――――――――――――――――――┐
r0 │ 0 │ │ 0101 0001 0000 1010 │ ← decimal -20746
r1 │ 1 │ │ 0000 0000 0001 0011 │ ← decimal 19
r2 │ 0 │ │ 0111 1101 0110 0110 │ ← decimal -32102
... ... ...
... ... ...
r15 │ 1 │ │ 0110 01101 0000 1111 │ ← decimal 52495
└―――┘ └――――――――――――――――――――――┘
.
This sections provides detailed description for all SIM instructions. In this version each instruction has the same fixed length of 5 Bytes. The first byte is always an operation code (so-called opcode). The content of the other bytes depends on the instruction type. The supported types are presented below.
| Note: The terms instruction, operation and command are often used as synonyms. |
|---|
.
This type generally includes main arithmetical and logical instructions. The result of an operation is always saved into the first operand (op1), consequently, op1 must be a register number. The second operand (op2) can be either a value or a register number.
As it was previously said, each instruction consists of 5 Bytes. These bytes are described in the following list. Note that numbers of bytes in the instruction is agreed with numbers of items in the list.
- The opcode -- defines an action that should be done by the operation.
- The control byte -- contains some control information that can change the behavior of the instruction and the structure of the operands. The following list describes its bits:
- Not used and must be equal to
0. - Not used and must be equal to
0. - The type of the
op2. This bit defines ifop2is a register or value (0-- register,1-- value). - The sign of
op2. Note it is used only ifop2is a value, otherwise, it must be equal to0. This bit defines ifop2value is positive or negative (0-- negative,1-- positive). - Not used and must be equal to
0. - Not used and must be equal to
0. - Not used and must be equal to
0. - Not used and must be equal to
0.
- Not used and must be equal to
- The value of
op1. It is always a register number. - The least significant byte of
op2. - The most significant byte of
op2.
.
For example, let's study the following instruction:
Start address Start address
of the next instr of the instr
↓ ↓
N+5 N+4 N+3 N+2 N+1 N
↓ ↓ ↓ ↓ ↓ ↓
Next │ MSB of op2│ LSB of op2│ op1 │ Control │ Opcode │ Previous
instr │ 0100 0000 │ 0100 1101 │ 0000 0011 │ 0000 0100 │ 0100 1101 │ instr
││
sign of op2 ―┘│
type of op2 ―┘
It encodes some action (it is defined by the opcode 0100 1101) on two values. Meanwhile, this first value is taken from r3 and the second value is taken directly from op2 and it is equal to -16461.
| Opcode | Notation | Short Description |
|---|---|---|
0000 0001 |
AND |
Bitwise logical AND |
0000 0010 |
OR |
Bitwise logical OR |
0000 0011 |
XOR |
Bitwise exclusive OR |
A bitwise AND takes two binary representations of equal length and performs the logical AND operation on each pair of corresponding bits. For each pair, the result is 1 if the first bit is 1 and the second bit is 1; otherwise, the result is 0. For example:
# r3 stores `1 0000 0000 0000 0111`, i.e. (decimal 7)
# r5 stores `0 1110 0000 0000 0110`, i.e. (decimal -57350)
AND r3, r5;
# r3 is changed to `0 0000 0000 0000 0110`, i.e. (decimal 5).
Note that AND also changes the sign bit.
A bitwise OR takes two bit patterns of equal length and performs the logical inclusive OR operation on each pair of corresponding bits. For each pair, the result is 1 if the first bit is 1 or the second bit is 1 or both bits are 1; otherwise, the result is 0. For example:
# r3 stores `1 0000 0000 0000 0111`, i.e. (decimal 7)
# r5 stores `0 1110 0000 0000 0110`, i.e. (decimal -57350)
OR r3, r5;
# r3 is changed to `1 1110 0000 0000 0111`, i.e. (decimal 57351).
Note that OR also changes the sign bit.
A bitwise exclusive OR takes two bit patterns of equal length and performs the logical XOR operation on each pair of corresponding bits. The result in each position is 1 if only the first bit is 1 or only the second bit is 1, but will be 0 if both are 1 or both are 0. This is equivalent to being 1 if the two bits are different, and 0 if they are the same. For example:
# r3 stores `1 0000 0000 0000 0111`, i.e. (decimal 7)
# r5 stores `0 1110 0000 0000 0110`, i.e. (decimal -57350)
XOR r3, r5;
# r3 is changed to `1 1110 0000 0000 0001`, i.e. (decimal 57345).
Note that XOR also changes the sign bit.
| Opcode | Notation | Short Description |
|---|---|---|
1000 0010 |
ADD |
Integer addition |
1000 0011 |
SUB |
Integer subtraction |
1000 0001 |
MUL |
Integer multiplication |
1000 0000 |
DIV |
Integer devision |
1000 0100 |
MOV |
Move a value into a register |
The command ADD performs simple decimal integer addition of two values of equal length. For example:
# r3 stores `1 0000 0000 1000 0111`, i.e. (decimal 135)
# r5 stores `0 0000 0000 0000 0110`, i.e. (decimal -6)
ADD r3, r5;
# r3 is changed to `1 0000 0000 1000 0001`, i.e. (decimal 129 = 135 + (-6)).
The command SUB performs simple decimal integer subtraction of two values of equal length. The value from op2 is subtracted from the value from op1. For example:
# r3 stores `1 0000 0000 1000 0111`, i.e. (decimal 135)
# r5 stores `0 0000 0000 0000 0110`, i.e. (decimal -6)
SUB r3, r5;
# r3 is changed to `1 0000 0000 1000 1101`, i.e. (decimal 141 = 135 - (-6)).
The command MUL performs simple decimal integer multiplication of two values of equal length. For example:
# r3 stores `1 0000 0000 1000 0111`, i.e. (decimal 135)
# r5 stores `0 0000 0000 0000 0110`, i.e. (decimal -6)
MUL r3, r5;
# r3 is changed to `0 0000 0011 0010 1010`, i.e. (decimal -810 = 135 * (-6)).
The command DIV performs simple decimal integer division of two values of equal length. The value from op1 is divided by the value from op2. The result is rounded down. For example:
# r3 stores `1 0000 0000 1000 1000`, i.e. (decimal 136)
# r5 stores `0 0000 0000 0000 0110`, i.e. (decimal -6)
SUB r3, r5;
# r3 is changed to `0 0000 0000 0001 0110`, i.e. (decimal -22 = 136 / (-6)).
The command MOV copies the value from op2 into the register of op1. For example:
# r3 stores `1 0000 0000 1000 0111`, i.e. (decimal 135)
# r5 stores `0 0000 0000 0000 0110`, i.e. (decimal -6)
MOV r3, r5;
# r3 is changed to `0 0000 0000 0000 0110`, i.e. (decimal -6, a copy of r5).
| Note: floating point calculations are not supported in this version. You can work only with integer numbers. |
|---|
.
This type generally includes auxiliary arithmetical, logical and system instructions. The operand (op) must be a register number and the result is always saved into it.
As it was previously said, each instruction consists of 5 Bytes. These bytes are described in the following list. Note that numbers of bytes in the instruction is agreed with numbers of items in the list.
- The opcode -- defines an action that should be done by the operation.
- The control byte -- contains some control information that can change the behavior of the instruction and the structure of the operands. The following list describes its bits:
- Not used and must be equal to
0. - The sign. This bit is used in some commands that change the sign of a register.
- Not used and must be equal to
0. - Not used and must be equal to
0. - Not used and must be equal to
0. - Not used and must be equal to
0. - Not used and must be equal to
0. - Not used and must be equal to
0.
- Not used and must be equal to
- The value of
op. It is always a register number. - Not used and all bits must be equal to
0. - Not used and all bits must be equal to
0.
For example, let's study the following instruction:
Start address Start address
of the next instr of the instr
↓ ↓
N+5 N+4 N+3 N+2 N+1 N
↓ ↓ ↓ ↓ ↓ ↓
Next │ not used │ not used │ op1 │ Control │ Opcode │ Previous
instr │ 0000 0000 │ 0000 0000 │ 0000 0101 │ 0000 0010 │ 0100 1101 │ instr
│
└― sign
It encodes some action (it is defined by the opcode 0100 1101) on a value that is taken from r5. The sign bit is set to 1.
| Opcode | Notation | Short Description |
|---|---|---|
0100 0100 |
NOT |
Bitwise logical negation |
The bitwise NOT, or complement, is a unary operation that performs logical negation on each bit, forming the ones' complement of the given binary value. Digits which were 0 become 1, and vice versa. For example:
# r3 stores `1 0000 0000 0000 0111` (decimal 7)
NOT r3;
# r3 is changed to `0 1111 1111 1111 1000` (decimal 177770).
Note that NOT also changes the sign bit.
| Opcode | Notation | Short Description |
|---|---|---|
1100 0000 |
DEC |
Integer decrement |
1100 0001 |
INC |
Integer increment |
1100 0010 |
SSGN |
Set a new sign |
1100 0011 |
ISGN |
Sign inversion |
The command DEC decreases the value from op by one.
# r3 stores `1 0000 0000 0000 0111` (decimal 7)
DEC r3;
# r3 is changed to `1 0000 0000 0000 0110` (decimal 6).
The command INC increases the value from op by one.
# r3 stores `1 0000 0000 0000 0111` (decimal 7)
INC r3;
# r3 is changed to `1 0000 0000 0000 1000` (decimal 8).
The command SSGN takes the sign bit and set its value into sign bit of the register of op, i.e. changes its sign.
# r3 stores `0 0000 0000 0000 0111` (decimal -7)
SSGN 1, r3;
# r3 is changed to `1 0000 0000 0000 0111` (decimal 7).
The command ISGN invert the sign of the register of op, i.e. change positive to negative and vice versa.
# r3 stores `1 0000 0000 0000 0111` (decimal 7)
ISGN r3;
# r3 is changed to `0 0000 0000 0000 0111` (decimal -7).
.
| Note: There is no such operation in this version. |
|---|
.
In this version Simmy implements only functional simulation. It means that the simulator does not count neither clocks nor any other performance data. The main purpose of this mode is to check the functional correctness of the program. The only way to do that is to execute it.
You should create a C++ class called Simmy which implements a functional simulator. It should have only two public methods: its constructor and the Execute method.
The constructor receives a pointer to a byte array where instructions of a program is located. The Execute method executes a certain number of instructions (as many as defined by its parameter) beginning from the first instruction in the byte array. Note that the first byte of the first instruction (i.e. its opcode) is located in the first element of the array (bytes[0]). When the given number of instructions is executed the method returns a value of r0.
class Simmy {
public:
Simmy ( unsigned char* bytes, unsigned int lenght);
// execute certain number of instructions
int Execute ( unsigned int number_of_instrs);
};
You are free to add as many additional private method and classes as you want, but you cannot change these methods.
This section provides helpful example how you can organize reading and parsing of instructions from a byte array.
#include <iostream>
using namespace std;
int main()
{
// create a simple instruction basing on the following pattern:
//
// Start address Start address
// of the next instr of the instr
// ↓ ↓
// N+5 N+4 N+3 N+2 N+1 N
// ↓ ↓ ↓ ↓ ↓ ↓
// Next │ MSB of op2│ LSB of op2│ op1 │ Control │ Opcode │ Previous
// instr │ 0100 0000 │ 0100 1101 │ 0000 0011 │ 0000 0100 │ 0100 1101 │ instr
// ││
// sign of op2 ―┘│
// type of op2 ―┘
unsigned char bytes[6] = {
77, // 0100 1101
5, // 0000 0100
3, // 0000 0011
77, // 0100 1101
64 // 0100 0000
};
// get the start address of the instr
unsigned char* instr_start_addr = bytes;
// get the opcode value
unsigned char* opcode_addr = instr_start_addr;
unsigned char opcode = *opcode_addr;
// get the cotrol byte value
unsigned char* control_byte_addr = instr_start_addr + 1;
unsigned char control_byte = ( *control_byte_addr);
bool type_op2 = control_byte & 4; // != 0 only if the 3rd bit is 1;
bool sign_op2 = control_byte & 8; // != 0 only if the 4th bit is 1;
// get the value of op2
unsigned char* op2_addr = instr_start_addr + 3;
int val_op2 = *( ( unsigned short*) op2_addr); // take 2 bytes from op2_addr
// apply the sign
if ( !sign_op2 && type_op2) {
val_op2 = -1 * val_op2;
}
cout << "Opcode = " << ( unsigned short) opcode << "\n"
<< "Operand_2 = " << ( type_op2 ? "" : "r") << val_op2 << "\n";
return 0;
}