li t0, 3 loop: addi t0, t0, -1 bnez t0, loop
The loop loads 3 into t0 and subtracts 1 until t0 is 0. It looks trivial, but it contains all three topics: an instruction with a negative immediate, a backward conditional branch and a dependency between consecutive instructions.
1. Encoding: bnez is a bne with a negative offset
bnez does not exist in the ISA: it is a pseudo-instruction the assembler turns into bne t0, zero, loop. Since loop is 4 bytes before the branch, the offset is −4 and the resulting word is 0xFE029EE3. In the B format the immediate is split into four pieces and its bit 0 is not stored, because targets are always even:
| Field | Bits | Binary | Meaning |
|---|---|---|---|
| imm[12] | [31] | 1 | piece of the immediate |
| imm[10:5] | [30:25] | 111111 | piece of the immediate |
| rs2 | [24:20] | 00000 | register x0 (zero) |
| rs1 | [19:15] | 00101 | register x5 (t0) |
| funct3 | [14:12] | 001 | selects the operation within the group |
| imm[4:1] | [11:8] | 1110 | piece of the immediate |
| imm[11] | [7] | 1 | piece of the immediate |
| opcode | [6:0] | 1100011 | group BRANCH |
| imm | -4 (0xFFFFFFFC) | rebuilt immediate (signed) | |
| Instruction | bne t0, zero, -4 | ||
Joining imm[12], imm[11], imm[10:5] and imm[4:1] and appending the final 0 gives 1 1 111111 1110 0 = −4. Open it in the decoder.
The six formats at a glance
| Format | Fields (bit 31 → bit 0) | Used for |
|---|---|---|
| R | funct7 · rs2 · rs1 · funct3 · rd · opcode | register-register operations (add, sub, and, sll…) |
| I | imm[11:0] · rs1 · funct3 · rd · opcode | immediate operations, loads and jalr |
| S | imm[11:5] · rs2 · rs1 · funct3 · imm[4:0] · opcode | stores (sw, sh, sb) |
| B | imm[12|10:5] · rs2 · rs1 · funct3 · imm[4:1|11] · opcode | conditional branches (beq, bne, blt…) |
| U | imm[31:12] · rd · opcode | lui and auipc |
| J | imm[20|10:1|11|19:12] · rd · opcode | jal |
Notice that rs1, rs2 and rd always sit in the same positions. It is a RISC-V design decision: the hardware can start reading registers before knowing which instruction it is, at the cost of scattering the immediate bits.
2. Execution: follow the PC
When running, each instruction changes registers and the PC. bnez writes no register: it only decides whether the PC goes back to loop or falls through. The body runs 3 times, so 1 + 3 × 2 = 7 instructions execute and t0 ends at 0. In the debugger, press “Step” and watch the PC jump from 0x00000008 back to 0x00000004 twice.
3. Pipeline: what a branch costs
In a 5-stage pipeline the branch is not resolved until EX. By then the next two instructions have already been fetched, and if the branch is taken they must be discarded. The simulator treats every branch as taken, so the diagram shows exactly that 2-cycle penalty. The dependency of bne on addi (t0) costs nothing thanks to forwarding.
| Stage | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| IF | I0 | I1 | I2 | I3 | I2 | I3 | — | — | — | — |
| ID | — | I0 | I1 | I2 | FLUSH | I2 | I3 | — | — | — |
| EX | — | — | I0 | I1 | FLUSH | FLUSH | I2 | I3 | — | — |
| MEM | — | — | — | I0 | I1 | FLUSH | FLUSH | I2 | I3 | — |
| WB | — | — | — | — | I0 | I1 | FLUSH | FLUSH | I2 | I3 |
Open this example in the pipeline simulator.
Suggested path
- Guide: how to decode an RV32I instruction and the decoding exercises and formats.
- Guide: from machine code to assembly, which translates a whole routine with branches and labels.
- Debugger and the register tracing and loop exercises.
- Guide: the 5-stage pipeline and RAW hazards, forwarding and stalls, with the load-use exercise.