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 RISC de 32 bits;
Arquitetura do tipo Load/Store;
Possui 32 registradores de uso geral (alguns possuem uso bem definido);
Endereçamento de 2^30 palavras de memória (2^32 bytes = 4GBytes);
O endereço da memória tem granularidade de um byte (endereços de 32 bits).
O acesso à memória de instruçõe é feito com endereços que diferem de 4 bytes (uma word):
Formato regular para as instruções;
Não possui flags de estado;
Little Endian ou Big Endian, conhecido como Bi-Endian;
Não possui suporte de hardware para gerenciar pilhas;
Pipeline de 5 estágios;
Interrupção.
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.
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.
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:
| 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.
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.
É 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.

Abaixo, temos o funcionamento dos modos de endereçamento, representados na figura acima, ordenados pela numeração:
O operando é uma constante dentro da própria instrução;
O operando é um registrador;
O operando está no local de memória cujo endereço é a soma de um registrador e uma constante na instrução;
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
Esse grupo é composto pelas instruções listadas abaixo e serão estudadas nas aulas:
LW: carrega palavra da memória para um registrador;
SW: salva o conteúdo de um registrador para uma posição de memória;
ADD: soma dois registradores e armazena o resultado em um terceiro;
SUB: calcula a diferença entre dois registradores e armazena o resultado em um terceiro;
AND: executa a operação lógica AND entre dois registradores e armazena o resultado em um terceiro;
OR: executa a operação lógica OR entre dois registradores e armazena o resultado em um terceiro;
BEQ: desvia para um determinado destino se o conteúdo de dois registradores for igual;
SLT: compara o conteúdo de dois registradores e indica o resultado em um terceiro registrador, usando 0 ou 1.
Para codificar todas as instruções que trabalham com inteiros, o RISC V (RV32I) possui seis formatos básicos de instrução:
Tipo R para operações entre registradores (ADD, SUB, AND, OR e SLT);
Tipo I para operações com valores imediatos e loads (LW);
Tipo S para stores (SW);
Tipo B (também chamada de SB) para desvios condicionais (BEQ);
Tipo-U para valores imediatos longos (LUI);
Tipo-J (também chamada de UJ) para saltos incondicionais (JAL).

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:
Uma memória não programada (EPROM ou EEPROM);
Barramentos de memória desconectados;
Ou chips defeituosos.
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:
O primeiro, chamado de base, é o conteúdo do registrador definido na instrução (x[rs1]);
E o segundo, chamado de deslocamento, é gerado pela extensão do sinal do campo imediato (que é de 12 bits).
A extensão do sinal do valor imediato, cria um valor de 32 bits, onde:
Os 20 bits mais significativos (31~12) = {imediato[11]}. Ou seja, repete 20 vezes o valor do bit 11 do imediato;
Os 12 bits menos significativos (11~0) = imediato. Ou seja, o próprio imediato.
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:
O primeiro, chamado de base, é o conteúdo do registrador definido na instrução (x[rs1]);
E o segundo, chamado de deslocamento, é gerado pela extensão do sinal do campo imediato (que é de 12 bits).
A extensão do sinal do valor imediato, cria um valor de 32 bits, onde:
Os 20 bits mais significativos (31~12) = {imediato[11]}. Ou seja, repete 20 vezes o valor do bit 11 do imediato;
Os 12 bits menos significativos (11~0) = imediato. Ou seja, o próprio imediato.
O endereço é a soma do conteúdo do registrador com o seguinte agregado:
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.
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:
O subconjunto “B” possui as instruções do subconjunto “A” e adiciona as listadas abaixo:
A instrução de carga:
Carrega imediato para 20 bits MSB (load upper immediate: lui);
Soma o imediato de 20 bits ao PC (add upper immediate to PC auipc).
As instruções lógico-aritméticas:
Soma com imediato (addi);
E lógico com imediato (ANDI);
OU lógico com imediato (ORI);
As instruções de desvio:
Desvio se não igual (branch not equal: bne);
Salto e conecta (jump and link: jal);
Salto por registrador (jump and link register: jalr).
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