Back to Home

Learning the MIPS Assembler

MIPS · assembler for beginners · assembler

Learning the MIPS Assembler



    According to Wikipedia , MIPS is a microprocessor developed by MIPS Computer Systems (currently MIPS Technologies) and first implemented in 1985. There are a large number of modifications of this architecture, created specifically for 3D-modeling, fast processing of floating point numbers, multi-threaded calculations. Various versions of these processors are used in Cisco and Mikrotik routers, smartphones, tablets and game consoles.

    MIPS instructions are simple enough to understand, and it is from it that it is recommended to start learning assembler. What now, in fact, we will do.

    Program structure in MIPS assembler


    This is what the classic MIPS assembler program looks like.
    Everything that starts on a point is directives . Directive .datameans the beginning of a data segment, .text- the beginning of a code segment.
    All followed by a colon - a mark ( v:, main:, loop:and endloop:).
    All text following the sign #is comments .
    And what remains is, in fact, instructions and pseudo- instructions (macros).

    .data
      v: .word -1, -2, -3, -4, -5, -6, -7, -8, -9, -10
    .text
    .globl main
    main:	
      li    $t0, 0   # $t0 = 0 (variable a)
      li    $t1, 0   # $t1 = 0 (counter i)
      li    $t2, 10  # $t2 = 10 (count limit l)
    loop:
      slt   $t3, $t1, $t2
      beq   $t3, $zero, endloop
      la    $t3, V
      sll   $t4, $t1, 2
      addu  $t3, $t3, $t4
      lw    $t3, 0($t3)
      addu  $t0, $t0, $t3
      addiu $t1, $t1, 1
      b     loop
    endloop:


    Types in MIPS Assembler


    Here is a comparison table of the main types in C ++ and MIPS:
    Type Comparison Chart in C ++ and MIPS
    As you can see in the table, the choice of type in for a variable in MIPS is based only on the amount of memory that this variable will occupy. Please note that MIPS does not distinguish between signed and unsigned variables in this regard.

    Labels (characters)


    In the code above we used several tags.
    Labels (they are also called symbols or labels) are used to give “names” to addresses in memory. These characters are divided into 2 large classes: data labels (addresses of global variables that are in the section .data, in this case v:) and instruction labels (addresses of instructions in the section .text, for example main:, loop:).
    Data in a section is .datausually stored in memory starting at address 0x10010000. Instructions are stored starting at address 0x00400000. Since each MIPS assembler instruction takes exactly 32 bits, the following label-address table will be true for our program:
    Label Address Table
    Using labels is very convenient to work with global variables and other data from.data, But more on that later.

    Key Directives


    Мы уже рассмотрели несколько директив, а именно .data и .text, и уже известно, что первая предназначена для хранения данных и объявления глобальных переменных, а вторая – собственно для кода программы. Посмотрим на остальные директивы MIPS:
    • .globl sym
      объявляет символ sym глобальным и позволяет обращатся к нему из других файлов;
    • .extern sym size
      объявляет, что данные, которые хранятся в sym имеют размер size, и делает sym глобальной меткой (см. предыдущую директиву);
    • .ascii str
      сохраняет строку str в памяти, не добавляя нулевой символ (\0) в конец;
    • .asciiz str
      сохраняет строку str и добавляет в конец нулевой символ (\0);
    • .byte b1, b2, ..., bn
      sequentially stores bytes b1, b2, ..., bn in memory ;
    • .half h1, h2, ..., hn
      sequentially stores in memory 16-bit values h1, h2, ..., hn ;
    • .word w1, w2, ..., wn
      sequentially stores 32-bit values w1, w2, ..., wn ;
    • .dword dw1, dw2, ..., dwn
      sequentially stores in memory 64-bit values dw1, dw2, ..., dwn ;
    • .float f1, f2, ..., fn
      stores in memory floating-point numbers f1, f2, ..., fn ;
    • .double d1, d2, ..., dn
      saves floating point numbers (double precision) d1, d2, ..., dn ;
    • .space n
      allocate n bytes in this data segment;
    • .align n
      align all of the following data to 2 ^ n bytes.

    Regarding the last directive: let's say that .datawe wrote .align 1. In this case, even if we write in memory, for example, to the address 0x10010000 some value of 1 byte in size, the next byte will be left empty, and if we want to write another byte to the memory, it will already receive the address 0x10010002. In MIPS, automatic data alignment is enabled by default, and therefore you can write a 16-bit value ( .half) only to an even memory address (0x10010000, 0x10010002, but not 0x10010003), a 32-bit value only to an address that is a multiple of 4, but 64-bit - only to the address multiple of 8.

    Data format in .data


    Data is .datarecorded in a fairly free manner. You just need to specify the label, data type and value. This code contains several examples of correct data recording:

    .data
      var1:  .byte     'A', 0xF3, 127, -1, '\n'
      var2:  .half     -10, 0xffff
      var3:  .word     0x12345678
      var4:  .float    12.3, -0.1
      var5:  .double   1.5e-10
      var6:  .dword    0x1234567812345678
      str1:  .ascii    “i love mips\n"
      str2:  .asciiz   “zero-finished string"
      array: .space    100
    


    We’ll look at data types a bit deeper as they are used in the code.

    Registers


    One of the main parts of a MIPS processor is registers. The standard MIPS processor has 32 main registers and 32 more in the first coprocessor - a module that is used for floating point calculations. Each register has a size of 32 bits, respectively, a single type value is completely placed in it int. To store a type variable, longyou must use two registers at once. Each register can be accessed by its serial name and its common name. The general is a bit more human-readable. The following registers are available:

    • $ zero ($ 0) - a register that always contains the value 0 and is read-only;
    • $ at ($ 1) - temporary processor register;
    • $ v0- $ v1 ($ 2- $ 3) - for the results returned by functions;
    • $ a0- $ a3 ($ 4- $ 7) - for function arguments;
    • $ t0- $ t9 ($ 8- $ 15, $ 24- $ 25) - for temporary data, you can use as you like;
    • $ s0- $ s8 ($ 16- $ 23, $ 30) - for constant data, you can use it as you like;
    • $ k0- $ k1 ($ 26- $ 27) - reserved for the kernel of the operating system;
    • $ gp ($ 28) - pointer for global variables, practically not used;
    • $ sp ($ 29) - stack pointer, its value is always equal to the top address of the stack;
    • $ ra ($ 31) - the sun god the address of the instruction from which the function was called;
    • $ f0 - for the results returned by functions, with a floating point;
    • $ f4, $ f6, $ f8, $ f10, $ f16, $ f18 - for temporary data with floating point;
    • $ f12, $ f14 - for parameters of floating point functions

    MIPS Instructions


    Note. From now on, we will look at the MIPS processor, its instructions and add-ons, using the wonderful MIPS simulator called MARS , which can be downloaded from here . The MIPS implementation in this simulator is fully compliant.

    In the code at the beginning of the article, we have already identified all the functional parts of the program and defined the instructions and pseudo-instructions as what is not a comment, symbol (label) or directive. Pseudo-instructions are also called macros; they are transformed into one or more instructions during code execution. Here is a macro example:

    la rdest, addr
    goes to the set of instructions:

    lui $at, hi(addr)
    ori rdest, $at, lo(addr)


    As you can see, MIPS programs are always written with one instruction per line.

    Types of instructions


    There are three main types of MIPS assembler instructions:
    • type R (register). Three registers are used as operands - the destination register (abbr. $ Rd), the first argument ($ rs), and the second argument ($ rt). An example of such an instruction is the addition of three registers:
      add $t2, $t0, $t1
      In this case, the result of adding the values ​​in $ t0 and $ t1 will be placed in $ t2.
    • type I (immediate). The operands are two registers and a number. An example of type I instruction:
      addi $t3, $t2, 12
      After execution, the result of adding $ t2 and the number 12 will be placed in the register $ t3.
    • Type J (jump). The only operand is the 26-bit address where you want to go. Instruction manual
      j 128
      will go to the address 128 in .text.


    There are also instructions for coprocessors, but we will discuss them later.

    Syscall instruction


    syscall- one of the simplest, but at the same time one of the most significant instructions of the MIPS assembler. This is a service instruction, therefore it is considered separately from the rest. syscallused to access the operating system to perform actions that the processor itself is not able to perform. Before calling this instruction, you need to put the service code in the register $ v0 - a natural number from 1 to 12. Depending on the code, the operating system will perform one or another action. Here is a list of utility codes and the corresponding operating system actions that MARS supports:

    Syscall table

    All input and output occurs through the MARS console.

    Arithmetic instructions


    So, let's look at some basic arithmetic instructions. Some abbreviations will be used: rd- the register where the result is written, rs- the first argument, rt- the second argument. It may also occur imm16- a 16-bit integer or imm5- a 5-bit integer.
    • add rd, rs, rt
      the sum of rs and rt is written to the rd register. Gently, may cause overflow.
    • sub rd, rs, rt
      rd = rs - rt. You can also get an overflow.
    • addu rd, rs, rt
      almost the same as the previous instruction, but this cannot cause an overflow. For arithmetic calculations, it is preferable to use this instruction.
    • subu rd, rs, rt
      rd = rs - rt. Also without overflow, and therefore recommended for use.
    • addi rd, rs, imm16
      rt = rs + 16-bit integer. Like add, it can cause overflow.
    • addiu rd, rs, imm16
      the same, but without the possibility of overflow. Use it.


    By the way, imm16 is interpreted as positive by default. For instance:
    addiu $s1, $zero, 0xFFFF # $s1 = 0x0000FFFF (положительное значение)

    If you need to add a negative value, then you need to explicitly indicate this:
    addiu $s1, $zero, -0xFFFF # $s1 = 0xFFFF0001 (негативное значение в дополнении к 2)


    Let's take a look at the real calculations using these instructions. Take, for example, the following code (in C ++):
    int f = (g+h) - (i-j);
    And translate this code into MIPS instructions. First you need to calculate the value to the right of the '=' sign, and then assign it to the variable f. Suppose that the variable f will be in the register $ s0, g - in $ s1, h - in $ s2, i - in $ s3, and j - in $ s4. Here is the result:

    addu $t0, $s1, $s2 # t0 = s1 + s2 = g + h
    subu $t1, $s3, $s4 # t1 = s3 - s4 = i - j
    subu $s0, $t0, $t1 # s0 = f = t0 - t1 = (g+h) - (i-j)


    And now you can test the resulting code in MARS. Download the draft from here and open it in MARS:
    java -jar Mars_4_2.jar

    Add the code instead of the comment. Now you can execute it. First select Run -> Assemble:

    MARS Assemble operation

    Now uncheck Hexadecimal Values ​​to see the decimal values ​​in the registers, and select Run -> Go:

    MARS Run operation

    The value in $ s0 after program execution should be 12.

    Registers after execution

    To be continued


    In the next article, we will consider logical instructions, as well as the multiplication and division of integers. In it, we will try to work with memory and the stack. In the meantime, I suggest you try to rewrite this code here in the MIPS assembler:
    int a = b + c;
    int d = e + f;
    int g = a + b;
    int h = g + d;
    

    Thanks for attention!

    Read Next