Decodificar a mano una instrucción RV32I es un procedimiento mecánico. Si siempre sigues el mismo orden, los formatos con los bits «desordenados» dejan de ser un problema. Esta guía da el método y lo aplica a una instrucción de cada formato. Todas las tablas las genera el decodificador del sitio.
El método en cinco pasos
- Pasa la palabra a binario y numera los bits del 31 (izquierda) al 0 (derecha). Cada dígito hexadecimal son 4 bits.
- Lee el opcode (bits 6–0). Decide el grupo y, con él, el formato. Sin este paso no sabes qué significan los demás bits.
- Lee los campos fijos que tenga ese formato: rd (11–7), funct3 (14–12), rs1 (19–15), rs2 (24–20) y funct7 (31–25).
- Recompón el inmediato juntando sus trozos en el orden que marca el formato y extiende el signo desde el bit 31.
- Elige el mnemónico con funct3 (y funct7 cuando exista) y escribe la instrucción con los nombres ABI de los registros.
Tabla de opcodes de RV32I
| opcode | Grupo | Formato | Instrucciones |
|---|---|---|---|
| 0110011 | OP | R | add, sub, and, or, xor, sll, srl, sra, slt, sltu |
| 0010011 | OP-IMM | I | addi, andi, ori, xori, slti, sltiu, slli, srli, srai |
| 0000011 | LOAD | I | lb, lh, lw, lbu, lhu |
| 1100111 | JALR | I | jalr |
| 0100011 | STORE | S | sb, sh, sw |
| 1100011 | BRANCH | B | beq, bne, blt, bge, bltu, bgeu |
| 0110111 | LUI | U | lui |
| 0010111 | AUIPC | U | auipc |
| 1101111 | JAL | J | jal |
Faltan tres instrucciones de la base: FENCE (0001111) y ECALL/EBREAK (1110011). El decodificador del sitio no las reconoce.
Formato R: 0x40B50533
Opcode 0110011 → grupo OP. En formato R no hay inmediato: los 32 bits se reparten entre dos registros fuente, uno destino y dos campos de función. funct3 = 000 corresponde a add o sub, y funct7 decide: 0100000 es sub.
| Campo | Bits | Binario | Significado |
|---|---|---|---|
| funct7 | [31:25] | 0100000 | distingue variantes (0100000 = sub/sra) |
| rs2 | [24:20] | 01011 | registro x11 (a1) |
| rs1 | [19:15] | 01010 | registro x10 (a0) |
| funct3 | [14:12] | 000 | selecciona la operación dentro del grupo |
| rd | [11:7] | 01010 | registro x10 (a0) |
| opcode | [6:0] | 0110011 | grupo OP |
| Instrucción | sub a0, a0, a1 | ||
Formato I: 0xFFF28293
Opcode 0010011 → OP-IMM. Los bits 31–20 son el inmediato completo: 111111111111. Como el bit 31 es 1, al extenderlo a 32 bits se rellena con unos y el valor es −1. Resultado: addi t0, t0, -1.
| Campo | Bits | Binario | Significado |
|---|---|---|---|
| imm[11:0] | [31:20] | 111111111111 | trozo del inmediato |
| rs1 | [19:15] | 00101 | registro x5 (t0) |
| funct3 | [14:12] | 000 | selecciona la operación dentro del grupo |
| rd | [11:7] | 00101 | registro x5 (t0) |
| opcode | [6:0] | 0010011 | grupo OP-IMM |
| imm | -1 (0xFFFFFFFF) | inmediato reconstruido (con signo) | |
| Instrucción | addi t0, t0, -1 | ||
En los desplazamientos con inmediato (slli, srli, srai) los bits 24–20 son la cantidad de desplazamiento y los bits 31–25 hacen de funct7: 0100000 distingue srai de srli.
Formato S: 0xFEA42E23
Opcode 0100011 → STORE. Un almacenamiento no escribe ningún registro, así que el hueco de rd (bits 11–7) se usa para los 5 bits bajos del inmediato. El resto está en los bits 31–25: imm[11:5] = 1111111 e imm[4:0] = 11100. Juntos: 111111111100 = −4.
| Campo | Bits | Binario | Significado |
|---|---|---|---|
| imm[11:5] | [31:25] | 1111111 | trozo del inmediato |
| rs2 | [24:20] | 01010 | registro x10 (a0) |
| rs1 | [19:15] | 01000 | registro x8 (s0) |
| funct3 | [14:12] | 010 | selecciona la operación dentro del grupo |
| imm[4:0] | [11:7] | 11100 | trozo del inmediato |
| opcode | [6:0] | 0100011 | grupo STORE |
| imm | -4 (0xFFFFFFFC) | inmediato reconstruido (con signo) | |
| Instrucción | sw a0, -4(s0) | ||
La instrucción es sw a0, -4(s0): guarda a0 en la dirección s0 − 4. Fíjate en que rs2 (a0) es el dato y rs1 (s0) la base de la dirección.
Formato B: 0x00050663
Opcode 1100011 → BRANCH. El inmediato de un salto está en múltiplos de 2 bytes, así que su bit 0 no se guarda. Los otros 12 bits se reparten en cuatro trozos: imm[12] en el bit 31, imm[10:5] en 30–25, imm[4:1] en 11–8 e imm[11] en el bit 7.
| Campo | Bits | Binario | Significado |
|---|---|---|---|
| imm[12] | [31] | 0 | trozo del inmediato |
| imm[10:5] | [30:25] | 000000 | trozo del inmediato |
| rs2 | [24:20] | 00000 | registro x0 (zero) |
| rs1 | [19:15] | 01010 | registro x10 (a0) |
| funct3 | [14:12] | 000 | selecciona la operación dentro del grupo |
| imm[4:1] | [11:8] | 0110 | trozo del inmediato |
| imm[11] | [7] | 0 | trozo del inmediato |
| opcode | [6:0] | 1100011 | grupo BRANCH |
| imm | 12 (0xC) | inmediato reconstruido (con signo) | |
| Instrucción | beq a0, zero, 12 | ||
Ordenados: imm[12] = 0, imm[11] = 0, imm[10:5] = 000000, imm[4:1] = 0110, y el 0 implícito al final: 0 0 000000 0110 0 = 12. beq a0, zero, 12 salta 12 bytes (3 instrucciones) hacia delante si a0 vale 0.
Formato U: 0x123452B7
Opcode 0110111 → LUI. Los 20 bits altos son el inmediato y van directamente a los bits 31–12 del registro destino; los 12 bits bajos quedan a cero. Aquí t0 pasa a valer 0x12345000.
| Campo | Bits | Binario | Significado |
|---|---|---|---|
| imm[31:12] | [31:12] | 00010010001101000101 | trozo del inmediato |
| rd | [11:7] | 00101 | registro x5 (t0) |
| opcode | [6:0] | 0110111 | grupo LUI |
| imm | 74565 (0x12345) | inmediato reconstruido (con signo) | |
| Instrucción | lui t0, 0x12345 | ||
Formato J: 0x010000EF
Opcode 1101111 → JAL. Como en B, el bit 0 no se guarda y el resto se reparte: imm[20] en el bit 31, imm[10:1] en 30–21, imm[11] en el bit 20 e imm[19:12] en 19–12. Aquí solo imm[4] vale 1: el salto es de 16 bytes y la dirección de retorno se guarda en ra.
| Campo | Bits | Binario | Significado |
|---|---|---|---|
| imm[20] | [31] | 0 | trozo del inmediato |
| imm[10:1] | [30:21] | 0000001000 | trozo del inmediato |
| imm[11] | [20] | 0 | trozo del inmediato |
| imm[19:12] | [19:12] | 00000000 | trozo del inmediato |
| rd | [11:7] | 00001 | registro x1 (ra) |
| opcode | [6:0] | 1101111 | grupo JAL |
| imm | 16 (0x10) | inmediato reconstruido (con signo) | |
| Instrucción | jal ra, 16 | ||
¿Por qué los inmediatos están repartidos?
RISC-V fija dos cosas en todos los formatos: rs1, rs2 y rd están siempre en las mismas posiciones, y el bit de signo del inmediato es siempre el bit 31. Así el hardware puede leer los registros antes de saber qué instrucción es y la extensión de signo usa siempre el mismo cable. El precio es que los inmediatos de S, B y J quedan troceados. Entre S y B cambia muy poco: los bits se reutilizan desplazados una posición, porque en B el bit 0 no hace falta.
Errores habituales
- Leer los bits 31–20 como inmediato sin haber mirado antes el opcode: solo vale en formato I.
- Olvidar el 0 implícito al final de los inmediatos B y J (el desplazamiento sale la mitad).
- No extender el signo: 0xFFF en un inmediato I es −1, no 4095.
- En lui, olvidar que el inmediato ocupa los bits altos: lui t0, 1 carga 4096, no 1.
- Confundir rs1 y rs2 en los almacenamientos: el dato es rs2 y la base es rs1.