SimmySpecifficationVersion1 - MIPT-ILab/MDSP GitHub Wiki

Introduction

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.

.

Hardware specification


Registers


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
         └―――┘  └――――――――――――――――――――――┘

.

Instruction set


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.

.

Operations with two operands


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.

Instruction structure

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.

  1. The opcode -- defines an action that should be done by the operation.
  2. 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:
    1. Not used and must be equal to 0.
    2. Not used and must be equal to 0.
    3. The type of the op2. This bit defines if op2 is a register or value (0 -- register, 1 -- value).
    4. The sign of op2. Note it is used only if op2 is a value, otherwise, it must be equal to 0. This bit defines if op2 value is positive or negative (0 -- negative, 1 -- positive).
    5. Not used and must be equal to 0.
    6. Not used and must be equal to 0.
    7. Not used and must be equal to 0.
    8. Not used and must be equal to 0.
  3. The value of op1. It is always a register number.
  4. The least significant byte of op2.
  5. 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.

Bitwise logical operations

Opcode Notation Short Description
0000 0001 AND Bitwise logical AND
0000 0010 OR Bitwise logical OR
0000 0011 XOR Bitwise exclusive OR

AND

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.

OR

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.

XOR

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.

Integer arithmetics

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

ADD

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)).

SUB

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)).

MUL

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)).

DIV

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)).

MOV

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).

Floating point arithmetics

Note: floating point calculations are not supported in this version. You can work only with integer numbers.

.

Operations with single operand


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.

Instruction structure

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.

  1. The opcode -- defines an action that should be done by the operation.
  2. 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:
    1. Not used and must be equal to 0.
    2. The sign. This bit is used in some commands that change the sign of a register.
    3. Not used and must be equal to 0.
    4. Not used and must be equal to 0.
    5. Not used and must be equal to 0.
    6. Not used and must be equal to 0.
    7. Not used and must be equal to 0.
    8. Not used and must be equal to 0.
  3. The value of op. It is always a register number.
  4. Not used and all bits must be equal to 0.
  5. 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.

Bitwise logical operations

Opcode Notation Short Description
0100 0100 NOT Bitwise logical negation

NOT

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.

Integer arithmetics

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

DEC

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).

INC

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).

SSGN

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).

ISGN

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).

.

Operations without operands


Note: There is no such operation in this version.

.

Simulation


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.

External interfaces

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.

Getting instructions from memory

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;
}
⚠️ **GitHub.com Fallback** ⚠️