MIPS DLX

(baseado no capítulo 4 do livro texto)

A arquitetura do MIPS DLX é uma simplificação e atualização da arquitetura original do MIPS. Ela é, principalmente, utilizada para fins didáticos e existem implementações opensource.

Suas características principais são:


Filosofias do projeto MIPS:


Processador de 32 bits

Todas as instruções MIPS possuem 32 bits de comprimento e o acesso à memória é feito com granularidade de 32 bits.

O endereço de memória também é de 32 bits. Isso simplifica o hardware de carga e decodificação de instruções.

Como o MIPS não possui instruções que utilizam mais do que uma palavra (mais de um acesso à memória para obter a instrução completa), existe um número finito de instruções.

Apesar do acesso à memória ser de 4 bytes (32 bits), o endereçamento de memória é orientado a byte. Dessa forma, cada endereço se refere a uma posição de memória contendo apenas 1 byte (8 bits).

Isso implica que uma palavra do processador, quando armazenada na memória, ocupará 4 posições de memória (quatro endereços consecutivos) e deve iniciar em endereços que são múltiplos de 4. Este requisito é chamado de restrição de alinhamento.


Arquitetura do tipo Load/Store

O MIPS só pode fazer operações lógicas e aritméticas entre registradores ou entre registradores e constantes imediatas.

Um registrador recebe (load) um valor armazenado na memória através de uma instrução que só faz a carga. Por outro lado, o conteúdo de um registrador pode ser armazenado (store) na memória através de uma instrução específica para isso.


Banco de Registradores

O MIPS possui 32 registradores de uso geral e outros 32 registradores de ponto flutuante (que não serão abordados).

O hardware MIPS não impõe um uso específico para os registradores de uso geral (exceto r0). Ou seja, onde um registrador é necessário, qualquer registrador funcionará.

No entanto, a seguinte convenção, para o uso dos registradores, evoluiu como padrão para a programação MIPS. Ela é usada pela maioria das ferramentas, compiladores e sistemas operacionais:

Convenção de Uso dos Registradores
Registradores Nome Descrição Quem Salva
0 $zero Sempre retorna 0, não é modificável. A escrita é ignorada. N. A.
1 $at Reservado para uso do montador (assembler temporary). Rotina que Chama
2-3 $v0, $v1 Valores retornados pela sub-rotina. Rotina que Chama
4-7 $a0-$a3 (argumentos) Quatro primeiros parâmetros para a sub-rotina. Rotina que Chama
8-15 $t0-$t7 (temporários) Sub-rotinas pode usar sem salvar. Rotina que Chama
16-23 $s0-$s7 Para variáveis da sub-rotina, precisa restaurar antes de retornar. Rotina Chamada
24-25 $t8, $t9 (temporários) As sub-rotinas podem usar sem salvar. Rotina que Chama
26-27 $k0, $k1 Reservado para uso do manipulador da interrupção/trap. Rotina que Chama
28 $gp Ponteiro Global; usado para acessar variáveis "static" ou "extern". Rotina Chamada
29 $sp Ponteiro da pilha (stack pointer). Rotina Chamada
30 $s8/$fp Ponteiro da estrutura (frame pointer) ou nona variável da sub-rotina. Rotina Chamada
31 $ra Endereço de retorno para chamada de sub-rotina (JAL & JALR). Rotina que Chama


Existem outros registradores de uso específico:

Utilização dos Registradores

A identificação dos registradores começa com um cifrão ($). Em seguida, eles podem ter nomes ($zero a $ra) ou números ($0 a $31). Ao programar assembly para o MIPS, geralmente é melhor usar os nomes dos registradores.

O registrador $zero, sempre possui o valor 0 e é somente para leitura. A escrita é ignorada. A pseudoinstrução nop é traduzida para uma escrita nesse registrador.

O registrador $at (Assembler Temporary) é usado para valores temporários dentro de pseudoinstruções (move, li, la, etc...). Ele não é preservado através de chamadas de função.

Os registradores $v armazenam valores de retorno das funções. Não são preservados através de chamadas de função.

Os registradores $a0 até $a3 são usados para passar argumentos para funções. Eles não são preservados através de chamadas de função. Se houver mais argumentos, eles serão passados através da pilha.

Os registradores temporários ($t0 a $t9) são de uso geral. Não são preservados através de chamadas de função.

Os registradores "S" ($s0 a $s7) são de uso geral. Eles devem ser salvos na entrada de uma rotina e restaurados na saída dela.

Os registradores $k são reservados para uso do kernel do sistema operacional.

O registrador $gp é usado como ponteiro para a área que armazena as variáveis globais ou estáticas (Global Pointer). Esse ponto é a fronteira entre essas variáveis e a região que armazena as variáveis alocadas dinamicamente (Heap). Por isso, ele também serve de ponteiro base para essas variáveis.

O registrador $sp é usado como ponteiro para a pilha (Stack Pointer).

O registrador $fp é usado como ponteiro para a estrutura de pilha usada na chamada de uma sub-rotina (Frame Pointer). Caso não seja utilizada essa estrutura, ele pode ser usado como $s8.

O registrador $ra é usado como ponteiro para o endereço de retorno na chamada de sub-rotinas.


Endereçamento

No caso do MIPS, ele se restringe às instruções de leitura e escrita na memória. O endereçamento pode ser:

O endereçamento indexado é útil no acesso de vetores (arrays) e pilhas. É utilizado com o stack pointer e com o frame pointer.


Instruções

Classes das instruções

As instruções do MIPS podem ser divididas em 6 classes.

As 3 classes abordadas aqui:

E as 3 que não abordaremos:

A filosofia de projeto do MIPS considera que:

As instruções ficam armazenadas na mesma memória que o programa (arquitetura von Neumann) e possuem 4 bytes (32 bits), sempre com alinhamento de 4 bytes. Elas operam na memória e nos registradores.

Para facilitar o projeto, os 32 bits das instruções foram agrupados em campos e nomeados:

Opcode Rs Rt Rd shamt funct
6 bits 5 bits 5 bits 5 bits 5 bits 6 bits

O significado dos nomes dos campos:

Essa uniformidade na localização do opcode e dos endereços dos registradores facilita o projeto do hardware.

Formato dos tipos de instruções

O processador MIPS DLX possui 3 tipos distintos de formatos para as instruções: tipo R, I e J. Cada uma delas, utiliza de forma distinta os campos, podendo agrupá-los.

As instruções do tipo R (register) tem opcode 0x0 e trabalham com 3 registradores:

Formato Bits:
31 ~ 26 25 ~ 21 20 ~ 16 15 ~ 11 10 ~ 6 5 ~ 0
tipo R 0x0 Rs Rt Rd sem uso funct

As instruções do tipo I (immediate) trabalham com 2 registradores e um valor imediato:

Formato Bits:
31 ~ 26 25 ~ 21 20 ~ 16 15 ~ 0
tipo I opcode Rs Rd valor imediato (16 bits)

As instruções do tipo J (jump) trabalham com um operando:

São instruções de salto incondicional.

Formato Bits:
31 ~ 26 25 ~ 0
tipo J opcode valor imediato (26 bits)

Dependendo do destino desejado para o salto, os 26 bits do valor imediato podem não ser suficientes. Como todas palavras, na memória, são alinhadas a cada 4 bytes:

Ainda faltam 4 bits para especificar o destino do salto:


Flags, endianess e pilhas

A arquitetura MIPS não possui registradores (ou Flags) específicos para indicar se o resultado de uma operação possui qualificadores (se o resultado da operação é zero; se houve transbordo (overflow); se houve geração de vai um (carry); etc...).

A detecção dessas condições deve ser feita pelo programador, utilizando as instruções com prefixo SLT (set less than). Elas são avaliadas através do conteúdo dos registradores.

O MIPS não possui instruções específicas para trabalhar com pilhas (push e pop). É trabalho do programador, conforme o uso, administrar o valor do ponteiro da pilha.

O MIPS original podia utilizar a disposição de bytes, na memória e registradores, do tipo little endian ou big endian.

No caso do DLX, a disposição dos bytes é feita na forma big endian.

Transferência entre Registrador e Memória

Transferência entre Registrador e Memória


Execução das Instruções

Todas instruções do MIPS podem ser encaixadas na seguinte sequência de eventos:

As duas primeiras etapas são comuns a todas as instruções, indistintamente.

Etapa IF

Busca a próxima instrução na memória, utilizando o endereço no registrador PC (Program Counter), e armazena esta instrução no IR (Instruction Register).

Etapa ID

A etapa ID executa duas tarefas:

Existem três possibilidades para o próximo valor do PC:

Etapa EX

Utiliza a ALU para fazer os cálculos necessários com os operandos endereçados na etapa ID. Esses operandos podem vir de dois registradores ou um registrador e um valor imediato (codificado na instrução).

Etapa MEM

Faz a leitura e escrita de operandos na memória. Para isso, o resultado da ALU, com o endereço a ser acessado, é utilizado no acesso. No caso de uma escrita também deve estar disponível o valor a ser escrito na memória.

Como as instruções operam na memória ou em registradores, mas nunca em ambos, as instruções que não acessam a memória, transferem o valor da ALU diretamente para a próxima etapa (WB). Nesse caso, o resultado da ALU deve ser armazenado no registrador definido na instrução.

Etapa WB

Essa etapa escreve o valor recebido da etapa MEM no registrador definido na instrução. Para as instruções de escrita na memória, a etapa WB não é utilizada.


Pipeline

O pipeline é uma técnica que explora o paralelismo entre as instruções de um fluxo sequencial. Isso permite que, em um mesmo ciclo de clock, sejam executadas diferentes fases de diferentes instruções. Ele tem a vantagem de ser transparente ao programador.

O nome MIPS é a abreviação de Microprocessor without Interlocked Pipeline Stages. Isso, porque a versão original possuia um pipeline de 5 estágios sem intertravamento entre esses estágios.

Isso diz muito sobre o projeto original:

Para trabalhar com essas limitações, no projeto original, foi feito o seguinte:

Para resolver os outros problemas que poderiam ocorrer, como por exemplo:

O trabalho foi passado para o tempo de compilação (ou montagem), onde é feita a análise do fluxo de instruções e são inseridos NOPs para evitar essas ocorrências.

Assim, os programas para o MIPS original eram forçados a ter uma quantidade significativa de instruções NOP.

No DLX foi utilizada outra abordagem:

Além disso, as instruções mais longas podem pausar (intertravar) a atividade do pipeline (chamado de stall ou criação de bolha) até que essas instruções possam ser concluídas.


Interrupção

Uma interrupção é a alteração na sequência de execução do programa. Ela pode ser assíncrona, como uma interrupção de hardware, ou síncrona, como uma interrupção de software (syscall e break), também conhecida como trap.

O funcionamento é da seguinte forma:

Os registradores EPC e Cause, não fazem parte do banco de registradores do MIPS. Para verificar o seu conteúdo, deve-se usar a instrução de movimento: mfc0 (move from coprocessor 0).

O coprocessador 0, no MIPS, é chamado de MIPS processor control e é o responsável pela interrupção e diagnósticos do processador.

Para transferir o registrador Cause para o registrador $t0 utiliza-se:

   mfc0   $t0,   Cause;

Para transferir o registrador EPC para o registrador $k0 utiliza-se:

   mfc0   $k0,   EPC;

E para retornar ao ponto do programa onde ele foi interrompido:

   jr     $k0;

Ir para o início do documento.