DEV Community

ddupard
ddupard

Posted on

RV32I vs x86_32 Comparison

Introduction

There are several major differences between a RISC processor (such as the 32-bit RISC-V executing the RV32I or RV32G instruction sets) and a CISC processor (such as the i386).

The first difference that comes to mind is, of course, the size of the instruction set: between 1,000 and 1,500 instructions for the i386 compared to around 40 for a RISC-V processor running the base RV32I instruction set.

However, there is another, more subtle and less obvious difference: in a CISC processor, instruction length is variable and can range from 1 byte to more than 10 bytes. In contrast, on a RISC processor, the instruction size is fixed (except when using compressed instructions) and equals exactly 4 bytes (32 bits) for an RV32I processor.

Being limited to 4 bytes to encode an instruction introduces specific hardware and assembly constraints. The most immediate one leads to a simple question:

How do you load a 32-bit immediate value into a 32-bit register on a 32-bit RISC-V processor?

To answer this, let us first examine how this is handled on an i386 processor:

.intel_syntax noprefix
.section .text               # Indicates that the following section contains executable code
.global _start              # Defines the entry point of the program

_start:
    mov eax, 0x12345678     # Loads the hexadecimal value 0x12345678 into the eax register

Enter fullscreen mode Exit fullscreen mode

After assembling with GNU as and disassembling with objdump, we obtain:

x86_32:     file format elf32-i386


Disassembly of section .text:

08049000 <_start>:
8049000:  b8 78 56 34 12        mov    eax,0x12345678

Enter fullscreen mode Exit fullscreen mode

This is a direct translation of the assembly instruction: the opcode b8 is followed by the full 32-bit constant 0x12345678 (encoded in little-endian as 78 56 34 12), producing a single 5-byte instruction.


What happens on a 32-bit RISC-V processor?

.section .text             
.global _start             

_start:
    li t0, 0x12345678      # load 0x12345678 into register t0

Enter fullscreen mode Exit fullscreen mode

The assembly file above is translated into a binary that disassembles into the following code:

riscv:     file format elf32-littleriscv


Disassembly of section .text:

00010074 <_start>:
   10074: 123452b7            lui t0,0x12345
   10078: 67828293            addi  t0,t0,1656 # 0x678 in hex

Enter fullscreen mode Exit fullscreen mode

Because a 32-bit RISC-V instruction must fit within exactly 32 bits, it cannot contain a 32-bit immediate payload while simultaneously reserving bits for the opcode and the target register field. As a result, the assembler splits the operation into two hardware instructions:

  • lui t0, 0x12345 (Load Upper Immediate, U-Type format): loads the upper 20 bits into t0 (shifted left by 12 bits).
  • addi t0, t0, 1656 (Add Immediate, I-Type format): adds the remaining 12-bit value (1656 in decimal = 0x678 in hex) to t0.

Pseudo-Instructions vs Real Instructions

It is important to note that li (Load Immediate) does not actually exist in the RISC-V hardware specification. It is a pseudo-instruction provided by the assembler to simplify programming. When writing RISC-V assembly, the toolchain automatically translates high-level constructs like li or la (Load Address) into standard physical instruction sequences (lui + addi).

The Sign Extension Trap

A critical subtlety arises when loading arbitrary 32-bit values: the 12-bit immediate field in the addi instruction is sign-extended.

If bit 11 of the lower 12-bit payload is 1 (i.e., if the lower 12 bits fall in the range 0x8000xFFF), addi treats it as a negative value and subtracts it from the upper value. To compensate for this arithmetic effect, a compliant assembler must automatically detect when bit 11 is set and add 1 to the upper 20 bits loaded by lui.

Architectural Trade-offs: Hardware vs. Binary Size

This structural difference highlights the core design philosophy separating CISC and RISC architectures:

Feature CISC (x86_32) RISC (RISC-V RV32I)
Instruction Length Variable (1 to 15 bytes) Fixed (4 bytes / 32 bits)
32-bit Immediate Loading Single 5-byte instruction Two 4-byte instructions (8 bytes total)
Hardware Decoder Complexity High (must determine instruction boundaries dynamically) Very Low (fixed alignment simplifies instruction fetch and pipeline)
Execution Impact Compact code, complex decoding logic Slightly larger code size, streamlined pipelining and execution

Epilogue

Building an assembler for a RISC-V processor may seem straightforward at first glance due to the reduced instruction set. However, subtleties arising from fixed-width instruction encodings, sign-extension compensation, and pseudo-instruction expansion introduce unexpected layers of complexity that every compiler and assembler author must carefully handle.

Top comments (0)