Este é o guia de bolso do RAVEN, nosso emulador RISC-V pensado para ensino. Use-o como referência rápida.
- Cada seção mantém as tabelas essenciais, acompanhadas de explicações em português claro.
- Vá direto para Tabelas de codificação quando só precisar rever o arranjo dos bits.
- Quer entender as diretivas do assembler? Pule para Comportamento do assembler e pseudoinstruções.
- Prefere aprender praticando? Use o tutorial interativo
[?]dentro do Raven e teste os trechos de código conforme avança.
O RAVEN foca no subconjunto RV32IMAF para que você entenda cada etapa do ciclo buscar → decodificar → executar sem distrações.
- Tamanho da palavra: 32 bits.
- Endianness: sempre little-endian (
{to,from}_le_bytes). - Program counter: avança 4 em cada instrução; desvios e saltos usam deslocamentos relativos ao PC.
- Registradores: nomes
x0…x31com os apelidos tradicionaiszero,ra,sp,gp,tp,t0…t6,s0/fp,s1,a0…a7,s2…s11. Escritas emx0/zerosão descartadas.
Ainda não implementados: instruções CSR. O RAVEN cobre RV32IMAF — inteiros base, multiplicação/divisão, atômicos (LR/SC + AMO) e ponto flutuante de precisão simples.
| Categoria | Instruções |
|---|---|
| Tipo R | ADD, SUB, AND, OR, XOR, SLL, SRL, SRA, SLT, SLTU, MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU |
| Tipo I (OP-IMM) | ADDI, ANDI, ORI, XORI, SLTI, SLTIU, SLLI, SRLI, SRAI |
| Loads | LB, LH, LW, LBU, LHU |
| Stores | SB, SH, SW |
| Branches | BEQ, BNE, BLT, BGE, BLTU, BGEU |
| Superiores / saltos | LUI, AUIPC, JAL, JALR |
| Sistema | ECALL, EBREAK (alias: HALT) |
Divisão por zero vira oportunidade de aprendizado: DIV, DIVU, REM e REMU encerram o emulador com uma mensagem clara em vez
do resultado “arquitetado”. A interrupção evidencia que algo inesperado ocorreu.
As tabelas abaixo mostram todos os layouts de 32 bits usados no RAVEN. Sempre que aparecer uma observação, ela lembra o alcance do imediato ou algum detalhe importante.
| Campo | Bits | Descrição |
|---|---|---|
| opcode | [6:0] | família da instrução |
| rd | [11:7] | registrador destino |
| funct3 | [14:12] | subtipo |
| rs1 | [19:15] | registrador fonte 1 |
| rs2 | [24:20] | registrador fonte 2 |
| funct7 | [31:25] | subtipo extra / seletor de MUL/DIV |
| Campo | Bits |
|---|---|
| opcode | [6:0] |
| rd | [11:7] |
| funct3 | [14:12] |
| rs1 | [19:15] |
| imm[11:0] | [31:20] |
- Imediatos com sinal, variando de -2048 a 2047.
- Shifts usam
shamtnos bits [24:20] comfunct7 = 0x00(SLLI,SRLI) ou0x20(SRAI).
| Campo | Bits |
|---|---|
| opcode | [6:0] |
| imm[4:0] | [11:7] |
| funct3 | [14:12] |
| rs1 | [19:15] |
| rs2 | [24:20] |
| imm[11:5] | [31:25] |
| Campo | Bits |
|---|---|
| opcode | [6:0] |
| imm[11] | [7] |
| imm[4:1] | [11:8] |
| funct3 | [14:12] |
| rs1 | [19:15] |
| rs2 | [24:20] |
| imm[10:5] | [30:25] |
| imm[12] | [31] |
- O assembler preenche um imediato de 13 bits (em bytes). O bit 0 é sempre zero, pois o alvo precisa estar alinhado em palavras.
| Campo | Bits |
|---|---|
| opcode | [6:0] |
| rd | [11:7] |
| imm[31:12] | [31:12] |
| Campo | Bits |
|---|---|
| opcode | [6:0] |
| rd | [11:7] |
| imm[19:12] | [19:12] |
| imm[11] | [20] |
| imm[10:1] | [30:21] |
| imm[20] | [31] |
- Imediato de 21 bits armazenado em bytes; o bit 0 fica em zero porque os alvos também são alinhados em palavras.
| Uso | Valor |
|---|---|
OPC_RTYPE |
0x33 |
OPC_OPIMM |
0x13 |
OPC_LOAD |
0x03 |
OPC_STORE |
0x23 |
OPC_BRANCH |
0x63 |
OPC_LUI |
0x37 |
OPC_AUIPC |
0x17 |
OPC_JAL |
0x6F |
OPC_JALR |
0x67 |
OPC_SYSTEM |
0x73 |
Tipo R (0x33):
funct3 = 0x0:ADD(funct7=0x00),SUB(funct7=0x20)funct3 = 0x1:SLLfunct3 = 0x2:SLTfunct3 = 0x3:SLTUfunct3 = 0x4:XORfunct3 = 0x5:SRL(funct7=0x00),SRA(funct7=0x20)funct3 = 0x6:ORfunct3 = 0x7:AND- Multiplicação/divisão usam
funct7 = 0x01com os mesmosfunct3:MUL,MULH,MULHSU,MULHU,DIV,DIVU,REM,REMU.
Tipo I OP-IMM (0x13):
funct3 = 0x0:ADDIfunct3 = 0x1:SLLIfunct3 = 0x2:SLTIfunct3 = 0x3:SLTIUfunct3 = 0x4:XORIfunct3 = 0x5:SRLI(funct7=0x00),SRAI(funct7=0x20)funct3 = 0x6:ORIfunct3 = 0x7:ANDI
Loads (0x03): LB (0x0), LH (0x1), LW (0x2), LBU (0x4), LHU (0x5).
Stores (0x23): SB (0x0), SH (0x1), SW (0x2).
Branches (0x63): BEQ (0x0), BNE (0x1), BLT (0x4), BGE (0x5), BLTU (0x6), BGEU (0x7).
JALR (0x67): usa funct3 = 0x0.
System (0x73): o RAVEN implementa três códigos: ECALL (0x00000073), EBREAK
(0x00100073) e HALT (0x00200073). ebreak é um breakpoint de debug retomável; halt
interrompe permanentemente apenas o hart atual e deve ser usado como marca semântica de "este hart
terminou aqui" nos exemplos.
O assembler do RAVEN é propositalmente simples para que você consiga acompanhar cada etapa:
- Comentários começam com
;ou#. - Operandos são separados por vírgula (
mnemonic op1, op2, ...). - Seções/diretivas suportadas incluem
.text,.data,.bss,.section,.word,.byte,.half,.ascii,.asciz/.asciiz,.spacee.align.
A tabela abaixo documenta os formatos aceitos no RAVEN e a forma exata (conceitual) de expansão usada pelo assembler.
| Pseudo | Formato aceito | Expansão (conceitual) | Observações |
|---|---|---|---|
nop |
nop |
addi x0, x0, 0 |
Não aceita operandos. |
mv |
mv rd, rs |
addi rd, rs, 0 |
Cópia entre registradores. |
li |
li rd, imm |
addi rd, x0, imm |
Imediato deve caber em 12 bits com sinal (-2048..2047). |
subi |
subi rd, rs1, imm |
addi rd, rs1, -imm |
-imm deve caber em 12 bits com sinal. |
j |
j label_ou_imm |
jal x0, label_ou_imm |
Imediato PC-relativo de 21 bits (bit 0 precisa ser par). |
call |
call label_ou_imm |
jal ra, label_ou_imm |
Salva endereço de retorno em ra (x1). |
jr |
jr rs1 |
jalr x0, rs1, 0 |
Salto indireto sem link. |
ret |
ret |
jalr x0, ra, 0 |
Retorna para o endereço em ra (x1). |
la |
la rd, label |
lui rd, hi(label) + addi rd, rd, lo(label) |
Usa divisão hi/lo com arredondamento (+0x800) para caber em 12 bits com sinal no low. |
push |
push rs |
addi sp, sp, -4 + sw rs, 0(sp) |
Decrementa sp em 4 e armazena rs no novo sp (convenção RISC-V full-descending padrão) |
pop |
pop rd |
lw rd, 0(sp) + addi sp, sp, 4 |
Lê rd do sp atual e incrementa sp em 4 |
print |
print rd |
addi a7, x0, 1000 + addi a0, rd, 0 + ecall |
Imprime valor de registrador. |
printStr |
printStr label |
addi a7, x0, 1001 + la a0, label + ecall |
Imprime string NUL-terminada (sem quebra de linha). |
printString |
printString label |
igual a printStr label |
Alias legado aceito pelo assembler. |
printStrLn |
printStrLn label |
addi a7, x0, 1002 + la a0, label + ecall |
Imprime string e adiciona quebra de linha. |
read |
read label |
addi a7, x0, 1003 + la a0, label + ecall |
Lê uma linha inteira para memória em label (NUL-terminada). |
readByte |
readByte label |
addi a7, x0, 1010 + la a0, label + ecall |
Grava 1 byte em label. |
readHalf |
readHalf label |
addi a7, x0, 1011 + la a0, label + ecall |
Grava 2 bytes (little-endian) em label. |
readWord |
readWord label |
addi a7, x0, 1012 + la a0, label + ecall |
Grava 4 bytes (little-endian) em label. |
jalejalrsão instruções reais da ISA (não pseudo) e também são suportadas diretamente. Emjal, você pode usarjal label(comrd=raimplícito) oujal rd, label.
Para observar a expansão final em execução, rode cargo run, monte o programa e acompanhe o traço de instruções decodificadas na interface.
O Falcon suporta um ABI estilo Linux (mínimo) e algumas extensões didáticas do próprio Falcon.
a7(x17): número do syscalla0..a5(x10..x15): argumentos- retorno em
a0(x10) (valores negativos significam-errno, representados comoi32 as u32)
- Coloque o número do syscall em
a7. - Coloque os argumentos em
a0..a5. - Execute
ecall. - Leia o retorno em
a0.
O Falcon implementa só um subconjunto pequeno. Quando um syscall não existe, o Falcon para a execução e mostra uma mensagem no console (isso ajuda no ensino, porque o erro fica explícito).
- Em sucesso, o syscall retorna um valor não-negativo em
a0(por exemplo, quantos bytes foram lidos/escritos). - Em erro, o Falcon usa o padrão Linux de
-errnoema0. Internamente isso fica emu32(porque os registradores sãou32):a0 = (-(errno as i32)) as u32.
a7 |
Nome | Notas |
|---|---|---|
63 |
read |
a0=fd, a1=buf, a2=count (somente fd=0). Lê bytes (por linha, adiciona \n). |
64 |
write |
a0=fd, a1=buf, a2=count (fd=1/2). Escreve count bytes da memória. |
93 |
exit |
a0=status. Encerra execução. |
94 |
exit_group |
Igual a exit por enquanto. |
Argumentos:
a0 = fd(Falcon suporta1(stdout) e2(stderr); por enquanto ambos aparecem no console)a1 = buf(ponteiro para bytes na memória)a2 = count(quantidade de bytes para escrever)
Retorno:
a0 = bytes_writtenou-errno
Exemplo curto (imprime "Hello!\n" e sai):
.data
msg: .ascii "Hello!"
.byte 10 # '\n' (o Falcon não interpreta escapes dentro de .ascii/.asciz)
.text
li a0, 1 # fd=stdout
la a1, msg # buf
li a2, 7 # count
li a7, 64 # write
ecall
li a0, 0
li a7, 93 # exit
ecallArgumentos:
a0 = fd(Falcon suporta apenas0: stdin)a1 = buf(ponteiro onde os bytes serão gravados)a2 = count(máximo de bytes para ler)
Retorno:
a0 = bytes_readou-errno
Notas importantes (simplificações didáticas):
- A entrada vem do console da UI e é por linha. Quando há uma linha disponível, o Falcon adiciona
\nao final e entrega como bytes. - Se não há entrada, o Falcon pausa a execução (PC não avança) e fica aguardando o usuário digitar algo na UI.
Exemplo curto (lê e faz echo):
.data
buf: .space 64
.text
li a0, 0 # fd=stdin
la a1, buf
li a2, 64
li a7, 63 # read
ecall
mv t0, a0 # n = bytes_read
li a0, 1 # fd=stdout
la a1, buf
mv a2, t0
li a7, 64 # write
ecall
li a0, 0
li a7, 93
ecallArgumentos:
a0 = status(código de saída)
Efeito:
- Encerra a VM “normalmente” (isso não é fault na UI).
a7 |
Nome | Usado por |
|---|---|---|
1000 |
falcon_print_int |
print rd |
1001 |
falcon_print_zstr |
printStr / printString |
1002 |
falcon_print_zstr_ln |
printStrLn |
1003 |
falcon_read_line_z |
read label |
1010 |
falcon_read_u8 |
readByte label |
1011 |
falcon_read_u16 |
readHalf label |
1012 |
falcon_read_u32 |
readWord label |
Nas extensões read* do Falcon, ele insiste até receber um valor válido para o tamanho pedido. Entradas inválidas geram uma mensagem
amigável e o PC não avança, destacando que a execução está em pausa.
Pronto para ir além? Abra o tutorial interativo [?] no Raven para ver exemplos guiados ou explore Program Examples/ e enxergue
essas codificações em programas reais.