Compilando o comando FOR no MIPS

instrução MIPS LW e SW IF Simples no MIPS
Este post faz parte da série MIPS

Olá pessoal! Hoje vou mostrar para vocês como codificar, em Assembly MIPS, o comando de controle FOR. Primeiro mostro um código em C, o qual será usado como base de raciocínio lógico. No MARS, usaremos essa lógica para a codificação. Neste exemplo vamos colocar em prática praticamente tudo o que já aprendemos durante esses três anos de caminhada! Vou fazer passo a passo para o entendimento ficar mais fácil, comentando blocos de código. Se você tiver dúvidas a respeito das instruções, sugiro consultar os artigos anteriores, para verificar se consegue por eles é possível resolver. Caso não consiga mesmo entender, deixe sua dúvida nos comentários tá bom. Prontos para mais este desafio?

Código em C

#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>

int Vetor[10];
int indice, soma = 0, med =0;
int main() {          
    soma(Vetor);           
    media();             
    return 0;
}

void soma(int Vetor[]){    
    for(indice=0; indice<10; indice++) {        
         soma = Vetor[indice] + soma;
    }    
    printf("\nSoma: %d", soma);
}

void media(){    
    med = soma / 10;    
    printf("\nMedia: %d", med);
}

Codificando em Assembly MIPS

O primeiro passo é definirmos os dados que vamos usar no programa Assembly:

.data    
   $LS: .asciiz " a soma é: "    
   $LM: .asciiz " a media é: "    
   vetor: .word 0, 1, 2, 3, 4

No espaço de dados defino dois Labels, um LS para o texto que será impresso no console para a soma, e LM para o texto da média. Em seguida defino um vetor de 5 elementos com os valores de 0 a 4. Escolhi assim para ficar mais fácil acompanhar o cálculo na execução do MARS e corrigir possíveis erros. Depois começamos o espaço do código, definindo o programa principal:

.text
.globl main

Feito isto, temos de fazer todas aquelas configurações da pilha do procedimento principal. Então escrevi o label main para identificar o inicio e as quatro linhas seguintes são referentes a pilha.

main:   
  # configurações do programa principal    
   subu $sp, $sp, 32   # cria um frame de pilha com 32 bytes   
   sw $ra, 20($sp)     # salva o registrador $ra    
   sw $fp, 16($sp)     # salva o registrador $fp    
   addiu $sp, $sp, 28  # alinhamento de memória   

As próximas linhas são referentes às chamadas dos procedimentos.

  la $a0, vetor # carrega o vetor    
  jal soma      # chama o procedimento soma    

# Move o conteúdo do registrador de retorno ($v0) para o registrador 
# ($a1), liberando-o para ser usado novamente   
  move $a1, $v0       

# chama o procedimento media    
  jal media                 

  move $a1, $v0 # libera $v0

Carregamos o vetor para o registrador $a0, que é o primeiro parâmetro do procedimento, usando a instrução la. A instrução jal será executada depois que o procedimento soma terminar. Liberando $v0, conseguimos então chamar na sequencia o procedimento media. Novamente precisamos liberar $v0 copiando seu conteúdo para $a1. As próximas linhas são referentes à impressão no console das strings e dos valores inteiros retornados pelos procedimentos:

.text

 # imprimindo a string   
  li $v0, 4   
  la $a0, $LS   
  syscall 

 # imprimindo o inteiro   
  li $v0, 1   
  move $a0, $s1   
  syscall 

 # imprimindo a string   
  li $v0, 4   
  la $a0, $LM   
  syscall 

 # imprimindo o inteiro   
  li $v0, 1   
  move $a0, $s2   
  syscall

Note que para os dois procedimentos foi necessário replicar código. Para a soma é necessário passar como parâmetro o label $LS e para a média o $LM. Além disso, o resultado da soma será armazenado no registrador $s1, e o da média em $s2, por isso eles são usados na chamada de sistema para impressão de números inteiros. Tome muito cuidado com esses detalhes, é muito fácil confundir labels, registradores, etc. Preste bastante atenção quando estiver fazendo seus próprios códigos. As próximas linhas são referentes à configuração do programa principal.

# configurações do programa principal    
lw $ra, 20($sp)       # restaura valor de $ra    
lw $fp, 16($sp)       # restaura valor de $fp    
addiu $sp, $sp, 32    # remove o frame de pilha  
j fim                 # encerra o programa

Agora vamos ver o procedimento da soma, que tem um for para fazer a soma de todos os elementos do vetor. Primeiro defini um label para o procedimento, chamado soma, e em seguida fiz todas as configurações da pilha de procedimento. Nunca se esqueça deles!!! Esse trecho de código você sempre pode utiliza-lo como padrão quando usar procedimentos.

.textsoma:    
# configurações do procedimento    
subu $sp, $sp, 32   # reserva o espaço do frame    
sw $ra, 20($sp)     # salva o endereço de retorno    
sw $fp, 16($sp)     # salva o frame pointer    
addiu $fp, $sp, 28  # prepara o frame pointer    
sw $a0, 0($fp)      # salva o argumento

Próximo passo é carregar o vetor para ser usado no procedimento, assim como definir um registrador para guardar o número total de elementos ($s3) no vetor e outro que guardará a soma ($s1). Esses registradores serão usados no FOR.

la $s4, vetor # indice do vetor   
li $s3, 5 # número total de elementos no vetor   
li $s1, 0 # soma  

Chegamos na parte que é novidade, como codificar o FOR no MIPS. Antes de iniciar a parte do código referente ao LOOP, precisamos definir i = 0. O registrador que usarei para isto será o $s0. Assim, movi o valor zero, que está no registrador $zero, para $s0. Pronto, a variável i foi inicializada com zero, lembrando que o nosso i é o índice do vetor.

#FOR  
move $s0, $zero    # i = 0 ($s0 é i)

Feito isto, comecemos o LOOP. O FOR é uma estrutura de controle como o WHILE e o DO-WHILE. Para escolher para onde ir, vamos precisar usar a instrução SLT juntamente com a BEQ, como já estudamos antes. Essas duas linhas também podem ser usadas como um padrãozinho toda vez que você for codificar um LOOP FOR. A instrução SLT verifica se i >= n, ou $s0>= $s3, que é a condição de parada aqui. Se for verdade, armazena zero em $t0, caso contrário, armazena 1. Bom, mas a instrução SLT, apenas armazena um valor 0 ou 1, ela não toma nenhuma decisão, então, quem é que faz isso? A instrução BEQ é quem faz esse papel, ela decide continuar, ou não, executando o código dentro do LOOP. Se já alcançamos o final do vetor, então, a execução deve parar (EXIT), mas enquanto não atingirmos o fim do vetor, o bloco deve continuar executando. Assim, se i>=n, sai do LOOP, caso contrário, continua executando.

LOOP:      
# configurações do FOR      
slt $t0, $s0, $s3      # t0 = 0 se $s0 >= $s3 ( i >= n), t0 = 1 caso contrário      
beq $t0, $zero, EXIT   # se $s0 >= $s3 ( i >= n) vá para EXIT

Depois de codificar corretamente a instrução FOR, pelo menos a primeira e a segunda parte dele, devemos agora tratar do ARRAY. Jamais se esqueçam do alinhamento de memória usado no MIPS. Temos de fazer aquele cálculo, padrãozinho também, toda vez que usarmos ARRAYs. Assim, usei a instrução SLL para tornar isso mais fácil, ela já faz o cálculo de 4*i juntamente com o registrador que vamos usar, que neste caso, é o $s0 (i). Em seguida, usei o registrador $s2 para armazenar o resultado final do endereço, que é a soma do endereço do vetor com o (4*i). Depois de tudo isso, podemos fazer o carregamento do elemento que está na posição que queremos, e então soma-lo com o valor que está em $s1 (que será a nossa soma final).

# configuração do ARRAY      
sll $t1, $s0, 2     # $t1 = 4 * i, ou 4 * $s0      
add $t2, $s4, $t1   # t2 = ( vetor + ( 4 * i) )            
lw $t3, 0($t2)      # $t3 = vetor[i], carregando o elemento do índice i      
add $s1, $s1, $t3   # somando os elementos (soma = soma + vetor[i]

Elemento carregado, somado e resultado guardado. Agora é hora de ir para a próxima posição do vetor. Como fazer isso? No FOR usamos um contador, i +=1, mas e no Assembly? Não é tão difícil ou complicado, só é preciso cuidar com os registradores que devem ser utilizados. Usei a instrução ADDI para somar o valor que está no índice i ($s0), com o imediato 1, assim incrementamos o índice e conseguimos passar para a próxima posição. A instrução J volta para o inicio do LOOP e o label EXIT sai desse bloco de código ao término da execução do LOOP.

# configurações do FOR      
addi $s0, $s0, 1  # $s0 = $s0 + 1 (ou i = i + 1) é o contador      
j LOOP            # volta para o LOOP  EXIT:

Finalizando o procedimento soma, temos de terminar as configurações referentes aos registradores. Passei $s1 na instrução ADD pois é este registrador que contem o resultado da soma, e que deve retornar ao programa principal. A instrução JR encerra o procedimento.

# configurações do procedimento    
add $v0, $s1, $zero # retorna para quem chamou.    
jr $ra

O procedimento media é exatamente a mesma coisa, só que mais fácil ainda, pois não há um FOR dentro dele a ser codificado. Dessa forma, as configurações do procedimento devem ser codificadas corretamente. Aqui eu usei a instrução DIV para calcular a média, portanto, $s3 é o numero total de elementos no vetor, e $s1 é a soma calculada no procedimento soma. O resultado é armazenado em $s2, o qual é passado na instrução ADD que retorna ao programa principal.

media:    
# configurações do procedimento    
subu $sp, $sp, 32   # reserva o espaço do frame    
sw $ra, 20($sp)     # salva o endereço de retorno    
sw $fp, 16($sp)     # salva o frame pointer    
addiu $fp, $sp, 28  # prepara o frame pointer    
sw $a0, 0($fp)      # salva o argumento    
li $s3, 5           # número total de elementos no vetor    
li $s2, 0           # media    
div $s2, $s1, $s3   # calcula a média    

# configurações do procedimento   
add $v0, $s2, $zero # retorna para quem chamou.    
jr $ra

Encerrando nosso “programinha” em Assembly MIPS, precisamo encerrá-lo corretamente:

fim:   
li $v0, 10   
syscall

A seguir você confere o código completo, para facilitar a visualização e também para que você teste em sua máquina.

.data   
  $LS: .asciiz " a soma é: "    
  $LM: .asciiz " a media é: "    
  vetor: .word 0, 1, 2, 3, 4

.text
.globl mainmain:    
  # configurações do programa principal    
   subu $sp, $sp, 32    
   sw $ra, 20($sp)    
   sw $fp, 16($sp)    
   addiu $sp, $sp, 28             
   la $a0, vetor    # chama o procedimento    
   jal soma    
   
   # Move o conteúdo do registrador de retorno ($v0) para outro 
   # registrador ($a1), para que seja liberado e usado novamente.   
     move $a1, $v0    
     jal media    
     move $a1, $v0.text  

   # imprimindo a string  
    li $v0, 4  
    la $a0, $LS   
    syscall 

   # imprimindo o inteiro   
     li $v0, 1   
     move $a0, $s1   
     syscall 

   # imprimindo a string   
     li $v0, 4   
     la $a0, $LM   
     syscall 

   # imprimindo o inteiro   
     li $v0, 1   
     move $a0, $s2   
     syscall  

   # configurações do programa principal    
     lw $ra, 20($sp)    
     lw $fp, 16($sp)    
     addiu $sp, $sp, 32  
     j fim   # encerra o programa

.text
soma:    
   # configurações do procedimento    
    subu $sp, $sp, 32   # reserva o espaço do frame    
    sw $ra, 20($sp)     # salva o endereço de retorno    
    sw $fp, 16($sp)     # salva o frame pointer    
    addiu $fp, $sp, 28  # prepara o frame pointer    
    sw $a0, 0($fp)      # salva o argumento    
    la $s4, vetor # indice do vetor    
    li $s3, 5 # número total de elementos no vetor   
    li $s1, 0 # soma    

   #FOR  
     move $s0, $zero     # i = 0 ($s0 é i)  
     LOOP:      
      # configurações do FOR      
      slt $t0, $s0, $s3      # t0 = 0 se $s0 >= $s3 ( i >= n), t0 = 1 caso contrário
      beq $t0, $zero, EXIT   # se $s0 >= $s3 ( i >= n) vá para EXIT      
  
      # configuração do ARRAY      
       sll $t1, $s0, 2      # $t1 = 4 * i (4 * $s0)      
       add $t2, $s4, $t1    # t2 = ( vetor + ( 4 * i) )      
       lw $t3, 0($t2)       # $t3 = vetor[i]      
       add $s1, $s1, $t3    # somando os elementos (soma = soma + vetor[i]    

      # configurações do FOR      
       addi $s0, $s0, 1       # $s0 = $s0 + 1 (ou i = i + 1) é o contador      
      j LOOP                 # volta para o LOOP  
      EXIT:    

   # configurações do procedimento    
    add $v0, $s1, $zero # retorna para quem chamou.    
    jr $ra

media:   
   # configurações do procedimento    
    subu $sp, $sp, 32   # reserva o espaço do frame    
    sw $ra, 20($sp)     # salva o endereço de retorno    
    sw $fp, 16($sp)     # salva o frame pointer    
    addiu $fp, $sp, 28  # prepara o frame pointer    
    sw $a0, 0($fp)      # salva o argumento   
    li $s3, 5 # número total de elementos no vetor    
    li $s2, 0 # media    
    div $s2, $s1, $s3 # calcula a média    

   # configurações do procedimento    
    add $v0, $s2, $zero # retorna para quem chamou.    
    jr $ra

fim:    
  li $v0, 10    
  syscall

Conclusão

E então pessoal? Acharam muito difícil? Se tiverem dúvidas, deixem aqui nos comentários, que responderei o mais breve possível. Para exercitar um pouco, sugiro vocês criarem mais procedimentos usando vetores. É isso galera, até o próximo artigo.

MIPS

MIPS: Subtração e outras instruções Ponto Flutuante no MIPS
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
2 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
joão
joão
06/01/2022 14:30

Por enquanto, Elaine, quero apenas te parabenisar por esse teu trabalho! o descobri recentemente tanto como venho descobrindo aos poucos esse novo mundo da computação. Já li, reli várias vezes algumas dessas postagens suas aqui, até entender, e com um prazer enorme te falo que tem ajudado bastante, sobretudo nesse período de pandemia! Que esse teu trabalho seja coroado de bençãos e que muitos bons e benéficos frutos (incluindo um dindim que nunca é demais!) possam surgir dai. Sou iniciante na computação e teu valioso material já é uma referencia pra mim. Grande obrigado!

Home » Hardware » Compilando o comando FOR no MIPS

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: