Guia ilustrado das instruções RISC-V RVI32 - Verilog
Fabio 30 Mar 2025 Verilog e riscvCada vez mais processadores e microcontroladores baseados em RISC-V vêm ganhando espaço no mercado como alternativas às arquiteturas ARM e MIPS. O RISC-V não é uma CPU específica nem uma empresa, mas sim um conjunto de instruções (ISA) aberto e de livre implementação, desenvolvido para ser modular, escalável e sem custos de licenciamento.
Esse padrão é mantido pela RISC-V International, uma organização sem fins lucrativos responsável por coordenar a evolução da arquitetura e suas especificações. Fundada em 2015 como RISC-V Foundation e transferida para a Suíça em 2020, a entidade atua para garantir neutralidade, continuidade e colaboração internacional. Com mais de 4.500 membros distribuídos em 70 países, a RISC-V International promove um ecossistema acessível e voltado à inovação, livre de royalties.
A arquitetura permite liberdade de design, sendo usada tanto em projetos abertos quanto proprietários. O conjunto de instruções RISC-V foi iniciado em 2010 por Krste Asanović, Yunsup Lee e Andrew Waterman no Par Lab da UC Berkeley, liderado por David Patterson. O Par Lab, financiado por Intel e Microsoft, visava avanços em computação paralela entre 2008 e 2013. O projeto RISC-V e a linguagem Chisel foram desenvolvidos como open source sob a licença BSD. Parte do financiamento veio da DARPA para implementação de processadores, mas não para a ISA RISC-V.
Na prática, isso quer dizer que as instruções são abertas e livres para implementação, porém não há nenhuma CPU com a marca RISC-V, as CPUs disponíveis no mercado com as instruções e extensões RISC-V são CPUs de empresas como SiFive, GigaDevices, Microchip, entre outras com projeto fechado e proprietário, e não deixariam de ser, visto que a fabricação física de uma CPU envolve projeto e processos fabris em microeletrônica protegidos e caros.
O interesse no RISC-V aliado a sua liberdade de uso faz com que algumas pessoas implementem sua própria CPU RISC-V, seja com emuladores, em Verilog ou VHDL, simulando em software ou sintetizados para alguma FPGA. De fato, pode-se inclusive comercializar uma implementação em FPGA, ao contrário do conjunto de instruções ARM.
Como fazer uma CPU envolve processos complexos, como conhecer uma linguagem de descrição de hardware (Verilog ou VHDL), conhecer arquitetura de CPUs, memória, barramento e eletrônica digital, este artigo foi feito para ajudar no entendimento da arquitetura RISC-V com ilustrações e trechos de códigos em Verilog e C. E espero que seja de grande ajuda!
Instruções Base RVI32
A especificação RISC-V divide-se em instruções Base e Extensões. Em 32 bits as instruções Base são conhecidas como RVI32, que contêm instruções de Movimentação, Soma, Lógicas, Saltos e de Sistema (não-protegido). Elas são divididas em 6 grupos de codificação de imediatos chamados R, I, S, B, U e J cujos formatos são ilustrados abaixo:
- opcode - 7 bits identificando cada grupo de instruções. opcode pode indicar um grupo de instruções ou uma instrução somente
- rd - 5 bits identificando um dos 32 registradores de destino do resultado da instrução
- rs1 - 5 bits identificando um dos 32 registradores fonte (source) numero 1
- rs2 - 5 bits identificando um dos 32 registradores fonte (source) numero 2
- funct3 - 3 bits que junto de funct7 identifica a instrução a ser executada do tipo determinado por opcode
- funct7 - 7 bits que junto de funct3 identifica a instrução a ser executada do tipo determinado por opcode
- imm[:] ou [] - São os valores numéricos da instrução que serão usados em algum cálculo interno de deslocamento ou armazenamento.
Como se pode observar, as instruções são todas em 32 bits o que facilita no fetch e decodificação da instrução. Como toda arquitetura RISC pura, o campo rs1, rs2 indicam os registradores que contém os operandos das instruções e rd o destino do resultado; não há instruções que efetuam cálculos com operandos ou destinos direto na memória, esses valores devem antes ser carregados nos registradores rs1 ou rs2 e o resultado da operação será guardado em rd e quando então a instrução store poderá gravar o conteúdo de qualquer registrador na memória. Se fosse o caso de um processador CISC poderíamos ter instruções de outros tamanhos além dos 32 bits e imediatos que sirvam de endereços para execução da instrução direto na memória.
Agora exploraremos esses 6 tipos detalhadamente.
Tipo R
As instruções do tipo R são as mais fáceis de se entender e um ótimo ponto de partida. São denominadas R justamente por operarem em operandos contidos em registradores, que foram previamente carregados pelas instruções tipo I, U ou resultado de uma operação anterior tipo R.
Na ilustração acima, ALU é a unidade aritmética que faz diversas operações como soma, diferença, xor, and entre outras. O campo opcode da instrução em RV32I com valor 0110011
identifica as instruções tipo R. Quem determinará qual operação a ALU efetuará será a combinação de funct7 e funct3 conforme tabela abaixo. rs1 e rs2 são o identificador de um dos 32 registradores que serão respectivamente o valor rs1 e rs2 da ALU e finalmente o resultado da operação aritmética será armazenado no registrador indexado por rd.
funct7 | rs2 | rs1 | funct3 | rd | op | instrução |
---|---|---|---|---|---|---|
0000000 | rs2 | rs1 | 000 | rd | 0110011 | ADD |
0100000 | rs2 | rs1 | 000 | rd | 0110011 | SUB |
0000000 | rs2 | rs1 | 001 | rd | 0110011 | SLL |
0000000 | rs2 | rs1 | 010 | rd | 0110011 | SLT |
0000000 | rs2 | rs1 | 011 | rd | 0110011 | SLTU |
0000000 | rs2 | rs1 | 100 | rd | 0110011 | XOR |
0000000 | rs2 | rs1 | 101 | rd | 0110011 | SRL |
0100000 | rs2 | rs1 | 101 | rd | 0110011 | SRA |
0000000 | rs2 | rs1 | 110 | rd | 0110011 | OR |
0000000 | rs2 | rs1 | 111 | rd | 0110011 | AND |
Veja que funct7 somente faz distinção das instruções ADD/SUB e SRL/SRA cujo funct3 são iguais.
Para extrair esses valores em Verilog utilize o código abaixo:
wire [6:0] OPCODE = INS[6:0];
wire [2:0] FUNCT3 = INS[14:12];
wire [6:0] FUNCT7 = INS[31:25];
wire [4:0] RS1_INDEX = INS[19:15];
wire [4:0] RS2_INDEX = INS[24:20];
wire [4:0] RD_INDEX = INS[11:7];
E no caso de uma emulador C/C++:
int opcode = ins & 127;
int funct3 = (ins & 28672) >> 12;
int funct7 = (ins & 4261412864) >> 25;
int rs1_index = (ins & 1015808) >> 15;
int rs2_index = (ins & 32505856) >> 20;
int rd_index = (ins & 3968) >> 7;
Esses imediatos e indexadores serão utilizados em outras instruções, sendo então reaproveitados nos outros tipos. Sendo essa uma das vantagens de um processador RISC moderno como o RISC-V, os imediatos têm posições fixas que facilitam a decodificação sem precisar de máquinas de estado (micro-código) e lógicas adicionais, o que torna o uso de portas lógicas o mínimo possível no RISC-V, permitindo então, fazer vários processadores, chamados harts, em uma pastilha de silício ou sintetizados em FPGA, ou seja, um multi-processador.
Tipo I
Instruções tipo I ou Imediato permitem carregar os registradores com valores através da ALU. Todas as instruções do tipo I fazem alguma operação aritmética no imediato da instrução, porém se o programador não quiser efetuar operação alguma no valor, basta somar com o registrador zero, e como toda soma de um número com zero é ele mesmo, então esse valor numérico imediato será armazenado no registrador sem alteração alguma. O x86 tem as instruções MOV, porém o RISC-V não tem uma instrução de carga explicita, somente aritméticas! Sim! Se você quer carregar um valor some ele com zero!
As instruções tipo I apenas diferem das tipo R por não usarem rs2 como operando, porém usam o valor imediato presente na instrução que deve ser extraído pelo decodificador de instruções.
Percebe-se na ilustração que a única diferença é a ausência de rs2 que foi substituído pela palavra de 32 bits montada através dos bits 31:20
da instrução que foram extraídos e concatenados nos bits 12:0
da palavra de 32 bits.
O sinal de imm[11:0]
, ou seja, o bit imm[11]
é estendido para o restante dos 32 bits da palavra, na prática, se copia esse bit aos 20 bits restantes da palavra. Algo fácil de se fazer em Verilog, porém em software é custoso.
Em seguida executa-se a operação da ALU com os operandos imm_i
e rs1 com destino em rd.
imm[11:0] | rs1 | funct3 | rd | op | instrução |
---|---|---|---|---|---|
xxxxxxxxxxxx | rs1 | 000 | rd | 0010011 | ADDI |
xxxxxxxxxxxx | rs1 | 010 | rd | 0010011 | SLTI |
xxxxxxxxxxxx | rs1 | 011 | rd | 0010011 | SLTIU |
xxxxxxxxxxxx | rs1 | 100 | rd | 0010011 | XORI |
xxxxxxxxxxxx | rs1 | 110 | rd | 0010011 | ORI |
xxxxxxxxxxxx | rs1 | 111 | rd | 0010011 | ANDI |
Para montar o Imediato segue código sugestivo em Verilog e C/C++:
wire [31:0] IMM_I = {{20{INS[31]}},INS[31:20]};
E no caso de um emulador C/C++:
int32_t imm_i = (ins >> 20) & 0xfff;
if (imm_i & 0x800) // se o bit 11 tá setado
imm_i |= 0xFFFFF000; // preenche o restante com 1s
Há outras formas em C/C++ de se fazer isso e é possível ainda otimizar utilizando instruções assembly que extraem bits com sinal estendido se houver na CPU que se escreve o emulador.
A instrução de salto incondicional JALR também utiliza o Tipo I de codificação de imediato, porém será explicada posteriormente.
O caso SLLI, SRLI e SRAI
As instruções de deslocamento à direita e a esquerda são do tipo I, porém tem tratamento diferente.
O imm[11:0]
contém duas informações, o shamt localizado no imm[4:0]
contendo a quantidade de deslocamento que pode ser 0 a 31 e a distinção entre SRLI
e SRAI
. Lembre-se de que não há sentido deslocar mais que 31 bits, pois a palavra tem somente 32 bits, logo shamt tem tamanho de 5 bits.
Finalmente, imm[30]
diferencia SRLI
de SRAI
já que ambos têm o mesmo funct3.
shamt | rs1 | funct3 | rd | op | instrução | |
---|---|---|---|---|---|---|
0000000 | xxxxx | rs1 | 001 | rd | 0010011 | SLLI |
0000000 | xxxxx | rs1 | 101 | rd | 0010011 | SRLI |
0100000 | xxxxx | rs1 | 101 | rd | 0010011 | SRAI |
A extração de shamt pode ser otimizada utilizando o mesmo código de rs2 e o discriminador com o mesmo código funct7.
Load e Store
As instruções de movimento da memória para o registrador e do registrador para a memória, Load e Store, têm o endereço armazenado em um dos registradores, que será denominado base, com a adição do imediato, denominado offset, de 12 bits codificado na instrução, também com sinal estendido, o que permite, então, apontar o endereço para cima ou para baixo do endereço apontado pelo registrador base.
Load
Load tem formato igual ao tipo I visto anteriormente. O imediato (offset) imm[11:0]
é somado ao registrador rs1 e o resultado é usado como endereço de leitura da memória para ser armazenado no registrador rd.
funct3 faz distinção de 5 tamanhos de leitura, sendo eles bytes (8 bits) LB e LBU, half-words (16 bits) LH e LHU e words (32 bits) LW e se os tipos bytes e half-words fazem extensão do sinal ou não (LBU e LHU) no armazenamento. Não confundir com o sinal estendido do imediato/offset, aqui me refiro ao valor carregado da memória que, se for ,por exemplo, um byte o bit de sinal desse byte ( bit 7 ) será estendido no registrador rd ou não.
imm[11:0] | rs1 | funct3 | rd | op | instrução |
---|---|---|---|---|---|
xxxxxxxxxxxx | rs1 | 000 | rd | 0000011 | LB |
xxxxxxxxxxxx | rs1 | 001 | rd | 0000011 | LH |
xxxxxxxxxxxx | rs1 | 010 | rd | 0000011 | LW |
xxxxxxxxxxxx | rs1 | 100 | rd | 0000011 | LBU |
xxxxxxxxxxxx | rs1 | 101 | rd | 0000011 | LHU |
Store
Store tem tipo próprio denominado tipo S. O imediato (offset) foi divido em duas partes na instrução e localiza-se nos bits ins[31:25]
e ins[11:7]
que juntos montam o imediato de 12 bits que novamente tem seu sinal estendido e é somado ao conteúdo do registrador rs1 para indicar o endereço da memória que será gravada com o conteúdo do registrador rs2.
Por ser uma CPU moderna e bem projetada, percebe-se que rd de outros tipos virou imm[4:0]
o que facilita na extração desses bits, permitindo utilizar código já existente.
imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | op | instrução |
---|---|---|---|---|---|---|
xxxxxxx | rs2 | rs1 | 000 | xxxxx | 0100011 | SB |
xxxxxxx | rs2 | rs1 | 001 | xxxxx | 0100011 | SH |
xxxxxxx | rs2 | rs1 | 010 | xxxxx | 0100011 | SW |
Tipo B
Esse conjunto reúne as instruções de salto condicional. Novamente aqui temos algumas diferenças que podem surpreender aqueles acostumados com x86, m68k: não há flags condicionais, as operações lógicas são efetuadas entre rs1 e rs2. BEQ, por exemplo, compara rs1 e rs2, se forem iguais, então salta para o destino, se diferente, continua na próxima instrução.
As instruções B carregam um imediato de 12 bits com sinal e como se pode ver na figura acima eles estão espalhados pela instrução. Perceba que no deslocamento final (32 bits) o bit 0
tem valor 0
; como as instruções no RISC-V tem tamanho 32 bits ou 16 bits, nenhuma instrução estará em endereço com bit[0] = 1
, sempre será com bit[0] = 0
, então justifica-se deslocar o deslocamento de 12 bits um bit a esquerda para aumentar o range de deslocamento do salto.
Novamente, o sinal é estendido no imediato final.
Montado o deslocamento final, funct3
seleciona a operação lógica entre rs1 e rs2, o resultado da operação é descartado, porém, se for verdadeiro o deslocamento somado ao PC da instrução será o pŕoximo PC, caso contrário a próxima instrução será executada (PC+4). Como o sinal é estendido o deslocamento pode ocorrer para frente do PC ou para trás em uma faixa (range) de +-4KiB.
Na ilustração, eu não atribuo a soma do deslocamento à ALU, isso é uma decisão do projetista, ele pode usar a ALU para somar o deslocamento ou usar um circuito separado para essa soma. As operações lógicas também podem ser executadas pela ALU ou em circuito separado.
Em arquitetura com pipeline, saltar para uma instrução significa descartar as instruções que já estão no pipeline.
[12] | imm[10:5] | rs2 | rs1 | funct3 | imm[4:1] | [11] | op | instrução |
---|---|---|---|---|---|---|---|---|
x | xxxxxx | rs2 | rs1 | 000 | xxxx | x | 1100011 | BEQ |
x | xxxxxx | rs2 | rs1 | 001 | xxxx | x | 1100011 | BNE |
x | xxxxxx | rs2 | rs1 | 100 | xxxx | x | 1100011 | BLT |
x | xxxxxx | rs2 | rs1 | 101 | xxxx | x | 1100011 | BGE |
x | xxxxxx | rs2 | rs1 | 110 | xxxx | x | 1100011 | BLTU |
x | xxxxxx | rs2 | rs1 | 111 | xxxx | x | 1100011 | BGEU |
Em Verilog a extração dos bits do imediato pode ser feita facilmente com o código sugerido abaixo:
wire [31:0] IMM_B = {{20{INS[31]}},INS[7],INS[30:25],INS[11:8],1'b0};
E no caso de um emulador C/C++:
int32_t imm_b = 0;
imm_b |= (INS & 0x80) << 4; // imm11
imm_b |= (INS & 0xf00) >> 7; // imm4_1
imm_b |= (INS & 0x7E000000) >> 20; // imm4_1
// Sinaliza (sign-extend) se bit 12 estava setado
if (INS & 0x80000000) { // se bit 12 está em 1
imm_b |= 0xFFFFE000; // extende o sinal até 32 bits
}
Tipo J, JAL e JALR
Há duas instruções de salto incondicional: JAL e JALR. JAL tem codificação do Tipo J e JALR do Tipo I. Ambas as instruções salvam o endereço da próxima instrução no registrador rd e somam um deslocamento ao PC atual.
JAL
O deslocamento de JAL é diretamente extraído do imediato de 20 bits contidos em sua instrução, enquanto na instrução JALR o deslocamento é uma soma do imediato de 12 bits e o registrador rs1. Dessa forma, JAL pode saltar em uma faixa (range) de até +-1MiB enquanto JALR pode saltar em toda a faixa de 32 bits desde que rs1 seja previamente carregado.
Observe que salvar a próxima instrução e saltar é uma chamada de sub-rotina com retorno, porém não há nenhum salvamento automático da próxima instrução na memória, a sub-rotina é que deverá preservar rd para poder retornar ao ponto posterior à chamada.
Se o salto não for para uma sub-rotina com retorno, basta chamar JAL/JALR com rd=x0
descartando assim PC+4
.
Como se pode ver na ilustração, a extração do imediato do JAL é complicada. O deslocamento consiste então em 20 bits com sinal somado ao PC atual. O PC+4
é gravado no registrador rd que poderá então ser utilizado como retorno na chamada da sub-rotina. Novamente percebe-se que a arquitetura RISC-V é mínima, não há instruções de retorno de sub-rotina nem de interrupção.
[20] | imm[10:1] | [11] | imm[19:12] | rd | opcode | instrução |
---|---|---|---|---|---|---|
x | xxxxxxxxxx | x | xxxxxxxx | xxxxx | 1101111 | JAL |
wire [31:0] imm_j = {{12{IF[31]}},IF[19:12],IF[20],IF[30:21],1'b0};
Em C/C++, a extração pode ser feita com código sugerido abaixo.
int32_t imm_b = 0;
imm_b |= (INS & 0b01111111111000000000000000000000) >> 20; // imm10_1
imm_b |= (INS & 0b00000000000011111111000000000000); // imm19_12
imm_b |= (INS & 0b00000000000100000000000000000000) >> 9; // imm11
if ((INS & 0x80000000))
imm_b |= 0xFFF00000;
JALR
Enquanto os saltos condicionais têm um range útil de 4KiB de salto e o JAL de 1MiB o JALR permite saltar por toda a faixa de 32 bits de endereçamento. Ela consegue fazer isso utilizando o registrador rs1 como endereço base que será somado ao imediato de 12 bits da instrução. Dessa forma, como o imediato só consegue um salto na faixa de 4KiB o conteúdo do registrador rs1 é que de fato permitirá saltos em toda a faixa de 32 bits.
Assim como JAL, JALR guarda PC+4
no registrador rd.
Como JALR é do Tipo I, a extração de seu imediato de 12 bits já foi explorada e o mesmo código poderá ser utilizado. Como toda a faixa de 32 bits é alcançável via rs1, ao contrário do JAL, JALR não tem o bit[0] == 0
, podendo então inclusive dar saltos em endereços não múltiplos de 2.
imm[11:0] | rs1 | funct3 | rd | op | instrução |
---|---|---|---|---|---|
xxxxxxxxxxxx | rs1 | 000 | rd | 1100111 | JALR |
Tipo U
Se você chegou até aqui, percebeu que a única forma de carregar um registrador até agora é com as instruções Tipo I que contêm um imediato de 12 bits. Mas como carregar o restante dos 20 bits de um registrador? Há duas instruções que complementam a carga dos registradores: LUI e AUIPC.
LUI
LUI (load upper immediate) carrega os 20 bits mais significativos do registrador rd com o valor imediato, zerando os 12 bits inferiores. Para carregar uma constante de 32 bits, a instrução LUI é usada em conjunto com uma instrução do Tipo I, como a ADDI, utilizando o registrador x0. De fato, é um casamento de instruções tão importante que a maioria dos compiladores utiliza uma macro chamada li
(load immediate) para carregar um registrador. Se o compilador detectar que a constante cabe em 12 bits, apenas uma instrução ADDI será emitida. Caso contrário, ele usará uma combinação de LUI e ADDI para compor o valor completo.
AUIPC
A instrução AUIPC (Add Upper Immediate to PC) soma um imediato ao valor atual do contador de programa (PC) e armazena o resultado no registrador rd.
Essa instrução é fundamental para permitir que executáveis sejam independentes da posição de memória (PIE) — como acontece em bibliotecas dinâmicas (DLLs), plugins e sistemas com ASLR.
Suponha que você precise chamar uma sub-rotina que, no momento da compilação, está localizada K bytes abaixo da instrução atual. Como o programa poderá ser carregado em posições diferentes da memória, você não pode usar um endereço absoluto.
Com AUIPC, é possível gerar um endereço relativo ao PC, somando o deslocamento K e armazenando o resultado em um registrador. Em seguida, você pode usar uma instrução como JALR para efetuar o salto para a sub-rotina.
imm[31:12] (20 bits) | rd (5 bits) | opcode (7 bits) | Instrução |
---|---|---|---|
xxxxxxxxxxxxxxxxxxxx | xxxxx | 0110111 | LUI |
xxxxxxxxxxxxxxxxxxxx | xxxxx | 0010111 | AUIPC |
A extração do imediato é direto:
Verilog
wire [31:0] imm_u = {INS[31:12],12'b0};
C/C++
int32_t imm_u = (int32_t)(INS & 0xFFFFF000);
Tabela resumo das instruções
Opcode [6:0] | Instrução | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
imm[31:12] |
rd |
0110111 |
LUI |
|||||||||||
imm[31:12] |
rd |
0010111 |
AUIPC |
|||||||||||
imm[20|10:1|11|19:12] |
rd |
1101111 |
JAL |
|||||||||||
imm[11:0] |
rs1 |
000 |
rd |
1100111 |
JALR |
|||||||||
imm[12|10:5] |
rs2 |
rs1 |
000 |
imm[4:1|11] |
1100011 |
BEQ |
||||||||
imm[12|10:5] |
rs2 |
rs1 |
001 |
imm[4:1|11] |
1100011 |
BNE |
||||||||
imm[12|10:5] |
rs2 |
rs1 |
100 |
imm[4:1|11] |
1100011 |
BLT |
||||||||
imm[12|10:5] |
rs2 |
rs1 |
101 |
imm[4:1|11] |
1100011 |
BGE |
||||||||
imm[12|10:5] |
rs2 |
rs1 |
110 |
imm[4:1|11] |
1100011 |
BLTU |
||||||||
imm[12|10:5] |
rs2 |
rs1 |
111 |
imm[4:1|11] |
1100011 |
BGEU |
||||||||
imm[11:0] |
rs1 |
000 |
rd |
0000011 |
LB |
|||||||||
imm[11:0] |
rs1 |
001 |
rd |
0000011 |
LH |
|||||||||
imm[11:0] |
rs1 |
010 |
rd |
0000011 |
LW |
|||||||||
imm[11:0] |
rs1 |
100 |
rd |
0000011 |
LBU |
|||||||||
imm[11:0] |
rs1 |
101 |
rd |
0000011 |
LHU |
|||||||||
imm[11:5] |
rs2 |
rs1 |
000 |
imm[4:0] |
0100011 |
SB |
||||||||
imm[11:5] |
rs2 |
rs1 |
001 |
imm[4:0] |
0100011 |
SH |
||||||||
imm[11:5] |
rs2 |
rs1 |
010 |
imm[4:0] |
0100011 |
SW |
||||||||
imm[11:0] |
rs1 |
000 |
rd |
0010011 |
ADDI |
|||||||||
imm[11:0] |
rs1 |
010 |
rd |
0010011 |
SLTI |
|||||||||
imm[11:0] |
rs1 |
011 |
rd |
0010011 |
SLTIU |
|||||||||
imm[11:0] |
rs1 |
100 |
rd |
0010011 |
XORI |
|||||||||
imm[11:0] |
rs1 |
110 |
rd |
0010011 |
ORI |
|||||||||
imm[11:0] |
rs1 |
111 |
rd |
0010011 |
ANDI |
|||||||||
0000000 |
shamt |
rs1 |
001 |
rd |
0010011 |
SLLI |
||||||||
0000000 |
shamt |
rs1 |
101 |
rd |
0010011 |
SRLI |
||||||||
0100000 |
shamt |
rs1 |
101 |
rd |
0010011 |
SRAI |
||||||||
0000000 |
rs2 |
rs1 |
000 |
rd |
0110011 |
ADD |
||||||||
0100000 |
rs2 |
rs1 |
000 |
rd |
0110011 |
SUB |
||||||||
0000000 |
rs2 |
rs1 |
001 |
rd |
0110011 |
SLL |
||||||||
0000000 |
rs2 |
rs1 |
010 |
rd |
0110011 |
SLT |
||||||||
0000000 |
rs2 |
rs1 |
011 |
rd |
0110011 |
SLTU |
||||||||
0000000 |
rs2 |
rs1 |
100 |
rd |
0110011 |
XOR |
||||||||
0000000 |
rs2 |
rs1 |
101 |
rd |
0110011 |
SRL |
||||||||
0100000 |
rs2 |
rs1 |
101 |
rd |
0110011 |
SRA |
||||||||
0000000 |
rs2 |
rs1 |
110 |
rd |
0110011 |
OR |
||||||||
0000000 |
rs2 |
rs1 |
111 |
rd |
0110011 |
AND |
||||||||
fm |
pred |
succ |
rs1 |
000 |
rd |
0001111 |
FENCE |
|||||||
1000 |
0011 |
0011 |
00000 |
000 |
00000 |
0001111 |
FENCE.TSO |
|||||||
0000 |
0001 |
0000 |
00000 |
000 |
00000 |
0001111 |
PAUSE |
|||||||
000000000000 |
00000 |
000 |
00000 |
1110011 |
ECALL |
|||||||||
000000000001 |
00000 |
000 |
00000 |
1110011 |
EBREAK |