Aula 11: Assembly do RISC V (RV32I)

Objetivos

  1. Familiarizar-se com a sintaxe e semântica das instruções do RISC V.

Contextualização

O processador que implementaremos é o RISC V, uma versão simplificada do original, com 32 bits (RV32I).

(baseado no capítulo 4 do livro texto)

Estudaremos o RISC V de 32 bits básico que trabalha somente com inteiros. Ele é conhecido como RV32I.

Suas características principais são:


Processador de 32 bits

Todas as instruções possuem 32 bits de comprimento e o acesso à memória é feito com granularidade de 32 bits (4 bytes por acesso).

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 RISC V 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 RISC V 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 RISC V possui 32 registradores de uso geral.

O hardware RISC V 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 RISC V. Ela é usada pela maioria das ferramentas, compiladores e sistemas operacionais:

Convenção de Uso dos Registradores
Registradores Nome Descrição Quem Salva
x0 zero Sempre retorna 0, não é modificável. A escrita é ignorada. N. A.
x1 ra Endereço de retorno para chamada de sub-rotina. Rotina que Chama
x2 sp Ponteiro da pilha (stack pointer). Rotina Chamada
x3 gp Ponteiro Global; usado para acessar variáveis “static” ou “extern”.
x4 tp Ponteiro Thread; identifica a thread sendo executada
x5-x7 t0-t2 (temporários) Sub-rotinas pode usar sem salvar. Rotina que Chama
x8 s0/fp Ponteiro da estrutura (frame pointer) ou primeira variável da sub-rotina. Rotina Chamada
x9 s1 Segunda variável da sub-rotina. Rotina Chamada
x10-x11 a0-a1 Argumentos: dois primeiros parâmetros para a sub-rotina ou valores de retorno. Rotina que Chama
x12-x17 a2-a7 Argumentos: parâmetros para a sub-rotina. Rotina que Chama
x18-x27 s2-s11 Para variáveis da sub-rotina, precisa restaurar antes de retornar. Rotina Chamada
x28-x31 t3, t6 Temporários: as sub-rotinas podem usar sem salvar. Rotina que Chama


Existem outros 32 registradores para ponto flutuante que não serão abordados.

Utilização dos Registradores

A identificação dos registradores começa com um “x” seguido de números de 0 a 31. Outra forma de identifcar é utilizando nomes, como zero, ra, t0 a t6, etc.

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.

Os registradores a0 até a7 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 t6) são de uso geral. Não são preservados através de chamadas de função.

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

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

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

O registrador tp é usado como ponteiro para a thread sendo executada.

Endereçamento

É a forma como é definido o operando a ser utilizado em uma instrução ou o endereço de destino para uma instrução de desvio.

Modos de Endereçamento do RISC V
Modos de Endereçamento do RISC V


Abaixo, temos o funcionamento dos modos de endereçamento, representados na figura acima, ordenados pela numeração:

  1. O operando é uma constante dentro da própria instrução;

  2. O operando é um registrador;

  3. O operando está no local de memória cujo endereço é a soma de um registrador e uma constante na instrução;

  4. O endereço de desvio é a soma do PC e uma constante na instrução;


Responder o quiz de participação, no blackboard, em:

Conteúdos > Participação > Aula_11_Quiz-1


Descrição das Instruções a Implementar (opção A)

Esse grupo é composto pelas instruções listadas abaixo e serão estudadas nas aulas:

Para codificar todas as instruções que trabalham com inteiros, o RISC V (RV32I) possui seis formatos básicos de instrução:

Formato das Instruções RV32I
Formato das Instruções RV32I

A instrução com todos campos contendo o valor 0 é ilegal no RV32I. Isso impede que uma região de memória que foi zerada seja executada como instrução.

A execução de uma instrução ilegal gera uma interrupção, indicando que o programa está em uma região incorreta e facilitando o debug.

Da mesma forma, a instrução com todos os bits com valor 1 também é ilegal. A sua execução gerará uma interrupção indicando a execução de uma região indevida. Isso pode ocorrer quando a instrução é carregada de:


Instruções de Referência à Memória

Carrega palavra (load word: lw)

Carrega um registrador, determinado na instrução (x[rd]), com a palavra localizada na posição de memória que é o resultado da soma do conteúdo do segundo registrador (também definido na instrução: x[rs1]) e o valor imediato. O imediato possui 12 bits.

Instrução Formato Funct3 / Opcode Operação (sintaxe estilo C)
lw x[rd], imediato(x[rs1]) I 0x2 / 0x03 x[rd] = MEM[endereço]; PC += 4;

O valor do endereço é dado pela soma de dois inteiros de 32 bits:

A extensão do sinal do valor imediato, cria um valor de 32 bits, onde:

O endereço é a soma do conteúdo do registrador com o seguinte agregado:

Armazena palavra (store word: sw)

Armazena o conteúdo de um registrador, definido na instrução (x[rs2]), na posição de memória determinada pela soma do conteúdo de um segundo registrador (também definido na instrução: x[rs1]) com o valor imediato. O imediato possui 12 bits.

Instrução Formato Funct3 / Opcode Operação (sintaxe estilo C)
sw x[rs2], imediato(x[rs1]) S 0x2 / 0x23 MEM[endereço] = x[rs2]; PC += 4;

O valor do endereço é dado pela soma de dois inteiros de 32 bits:

A extensão do sinal do valor imediato, cria um valor de 32 bits, onde:

O endereço é a soma do conteúdo do registrador com o seguinte agregado:


Instruções Lógicas e Aritméticas

Soma (add)

Soma o conteúdo de dois registradores, definidos na instrução, e armazena o resultado em um terceiro registrador (também definido na instrução), que pode, ou não, ser um dos dois anteriores. Adiciona o registrador x[rs2] ao registrador x[rs1] e grava o resultado em x[rd]. O overflow aritmético é ignorado.

Instrução Formato Funct7 / Funct3 / OpCode Operação (sintaxe estilo C)
add x[rd], x[rs1], x[rs2] R 0x0 / 0x0 / 0x33 x[rd] = x[rs1] + x[rs2]; PC += 4;

Subtração (sub)

Faz a subtração entre o conteúdo de dois registradores, definidos na instrução, e armazena o resultado em um terceiro registrador (também definido na instrução), que pode, ou não, ser um dos dois anteriores.

Subtrai o registrador x[rs2] do registrador x[rs1] e grava o resultado em x[rd]. O overflow aritmético é ignorado.

Instrução Formato Funct7 / Funct3 / OpCode Operação (sintaxe estilo C)
sub x[rd], x[rs1], x[rs2] R 0x20 / 0x0 / 0x33 x[rd] = x[rs1] - x[rs2]; PC += 4;

E lógico (and)

Executa a operação lógica “E”, bit a bit, entre o conteúdo de dois registradores, definidos na instrução, e armazena o resultado em um terceiro registrador (também definido na instrução), que pode, ou não, ser um dos dois anteriores.

Instrução Formato Funct7 / Funct3 / OpCode Operação (sintaxe estilo C)
and r[rd], r[rs1], r[rs2] R 0x0 / 0x7 / 0x33 x[rd] = x[rs1] & x[rs2]; PC += 4;

OU lógico (or)

Executa a operação lógica “OU”, bit a bit, entre o conteúdo de dois registradores, definidos na instrução, e armazena o resultado em um terceiro registrador (também definido na instrução), que pode, ou não, ser um dos dois anteriores.

Instrução Formato Funct7 / Funct3 / OpCode Operação (sintaxe estilo C)
or r[rd], r[rs1], r[rs2] R 0x0 / 0x6 / 0x33 x[rd] = x[rs1] | x[rs2]; PC += 4;

Comparação Menor Que (set if less than: slt)

Compara o conteúdo de dois registradores (definidos na instrução) e indica o resultado em um terceiro registrador (também definido na instrução). Usando o exemplo da tabela abaixo, se o conteúdo do registrador $s for menor que o conteúdo do registrador $t, o registrador $d recebe o valor 1. Caso contrário, $d recebe o valor 0.

Compara x[rs1] e x[rs2] como números de complemento de dois e escreve 1 em x[rd] se x[rs1] for menor, e 0 caso contrário.

Instrução Formato Funct7 / Funct3 / OpCode Operação (sintaxe estilo C)
slt x[rd], x[rs1], x[rs2] R 0x0 / 0x2 / 0x33 x[rd] = (x[rs1] < x[rs2] ? 1: 0); PC += 4;

Os registradores comparados contém inteiros com sinal.


Instruções de Desvio

Desvia Se Igual (branch equal: beq)

Compara o conteúdo de dois registradores definidos na instrução. Se forem iguais, desvia para o endereço definido por PC+extSinal(imediato). Caso contrário, continua na próxima instrução.

Instrução Formato Funct7 / Funct3 / bit 11 ~ 7 / OpCode Operação (sintaxe estilo C)
beq x[rs1], x[rs2], imediato B imediato[12|10:5] / 0x0 / imediato[4:1|11] / 0x63 if (rs1 == rs2) pc += sext(imediato) else PC += 4;

Ou seja, o endereço de desvio é a soma do PC e do agregado:


Instruções a Implementar (opção B)

O subconjunto “B” possui as instruções do subconjunto “A” e adiciona as listadas abaixo:

Funcionamento

Cabe ao aluno pesquisar o funcionamento dessas instruções.

Para ajudar na pesquisa, utilize os links existentes na descrição do projeto 2.


Responder o quiz de participação, no blackboard, em:

Conteúdos > Participação > Aula_11_Quiz-2



Atividade: Aula Estúdio