Comparison of the most common Instruction Set Architectures: x86, ARM and RISC-V
An Instruction Set is the list of all instructions a processor understands and can execute. It represents the interface through which software communicates with the processor which operations to perform. How the operation is performed inside the processor is decided by its implementation of the ISA, its microarchitecture. This abstraction layer allows existing software to run without any modifications on new processor generations with improved performance.
Given an existing compiler a program written in a high level language like C, C++ or Rust can be compiled for any instruction set.
Usage / Adoption
x86:
- personal computers desktop/mobile (AMD Ryzen, Intel Core) (x86 saw its first major success with original IBM PC in 1981)
- server market (AMD Epyc, Intel Xeon)
- gaming consoles: Playstation 5, Xbox Series X, Steamdeck
ARM:
- Embedded (Cortex M0, M3, M4)
- Smartphones (Apple A-Series, Qualcomm Snapdragon, Samsung Exynos, Mediatek)
- Server (Ampere Computing, Amazon Graviton, NVIDIA Grace)
- Mobile Personal Computers (Apple M Series, Qualcomm Snapdragon Elite)
- Raspberry Pi
RISC-V:
- embedded cores, micro-controllers (Western Digital SSD Controller, )
- Server CPUs (Ventana)
- Application CPUs (SiFive P Series )
- Automotive: Quintauris (Bosch, Infineon, NXP, Nordic Semi)
Legal
x86: not allowed to design own CPU, owned by Intel, AMD is also allowed to use it
ARM: buy license to design own CPU, licenses sold by company ARM
RISC-V: free open, , invented by University of California in Berkeley, managed today by non-profit organization RISC-V International
Year of Inception
ISA | Year | First Implementation in CPU |
---|---|---|
x86 | - 1978 (16 bit) - 1985 (32 bit) - 2003 (64 bit) |
Intel 8086, Intel 80386, AMD Athlon 64 |
ARM | - 1985 (32 bit) - 2011 (64 bit) |
32 bit: ARM1, 64 bit: Cortex-57, Apple A7 |
RISC-V | 2010 (32 and 64 bit) |
For this post, everytime x86 is mentioned implicitly x86-64 is meant.
Philosophy
x86 Complex Instruction Set Computing (CISC)
- lots of specialized instructions (motivated from a time where programmers needed to write in assembly, more complex instructions meant that they needed to write less instructions
- instructions may operate on CPU internal register or directly on memory addresses
- fewer registers
ARM, RISC-V: Reduced Instruction Set Computing (RISC)
- fewer instructions
- instructions generally operate on CPU internal registers, memory access only via ld/st
- more registers
Evolution / Extension / Development
x86 and ARM are incremental - new versions implement new instructions as well as all existing ones
- advantage: existing software can run on new CPU designs
- disadvantage: complex design and verification for old instructions that aren't commonly used anymore
RISC-V is modular - a RISC-V processor is free to implement certain ISA extensions, and others not
Instruction Count
x86: over 1500
ARMv8: > 1000 Src
For RISC-V I couldn't find any number. For an apples to apples comparison one would have to consider a RISC-V core that is designed as an application processor: capable of running an full operating system and arbitrary applications.
To provide one common instruction set that software developers can target, the RISC-V application class processor profile version 2023, RVA23 was created.
It makes several extensions (e.g. MAFDCV) mandatory. Adding all the instructions inside the extensions up, instruction count should reach several 100's, but probably less than ARM or x86.
Memory
x86 allows to operate on registers aswell as memory addresses
RISC-V only operates on registers
ARM only operates on registers
Registers
x86-64:
- only has 16 Register for Int + FP
- not general purpose, but perform special functions:
- operands for integer div come from ax and dx
- shift amount has to specified in cx
ARMv8 (AArch64)
- has 31 general purpose registers for Int and FP
RISC-V:
- has 31 general purpose integer registers + 32 floating point register, total 64 register
- register x0 contains a hardwired zero
- separate register for the program counter
Instruction Encoding
x86:
- instructions are between 1 and 15 byte long (16 bit and 120 bit)
- results are usually stored in the same register as one of the source operands, overwriting its value and forcing a reload when reused
- support for 24 different addressing modes
- registers have different names depending on their size

An access to all 64 bit of register a
, one has to call rax
, the lower 32 bit are called eax
, the lowest 16 bit are called ax
and the lowest 8 bit are called just al
.

ARMv8:
- instructions are 32 bit long
- register
RISC-V:
- instructions are 32 bit long,
- the destination register is specified inside the instruction, source operands don't have to be overwritten and reloaded
- for some common instructions, there exists a compressed format which reduces their storage size to 16 bit
- utilizing compressed instructions, the Linux Kernel took up only 70 % of the space compared to RISC-V 32 bit instructions (Src p. 60 to 64)
- other benefits are reduced bandwidth during instruction fetch, reduced cache misses
Example Integer Addition
To get a feeling how x86, ARM and RISC-V encode information in their instructions I'm going to compare an addition operation on two 64 bit integers. The assembly instruction for all three architectures is aptly named add
Inside the instruction each bit has a defined meaning. For example from where to load the operands, whether to perform any transformation on them before calculating the result and where to store the result. This meaning depends on the ISA.
The encoded information on how to exactly perform the addition is decoded at runtime by the "decoder" inside the CPU. By looking at 64 bit operations I want to highlight how x86 and ARM are handling the support of 64 bit operations, as they at their inception did not support such data widths. RISC-V was designed to support 32 bit, 64 bit and even future 128 bit systems from the beginning.
To allow a general comparison I'm not gonna use the terminology sum = summand + summand
but instead
result = (source) operand1 operation operand2
.
Example C-Code:
#include <stdint.h>
uint64_t add_function(a, b)
{
return a + b;
}
int main()
{
uint64_t c = add_function(8000000000, 1011000000000);
return 0;
}
RISC-V
On RISC-V the add
loads the two source operands from registers, adds them and stores them back into a register. The instruction has fixed length of 32 bit.
rs1
andrs2
specifies from which registers to load the operandsrd
specifies the destination register, where to store the result- source and destination register can be the same
The instruction encoding looks the following:

ADD X1, X2, X3
ARM (AArch64)
ARM provides more options on how perform the addition.
Rn
specifies from which register to load operand 1. Operand 1 is used as is.Rm
specifies from which register to load operand 2. Operand 2 may be sign-extended or zero extended.- The result of the addition can be shifted left (effectively doubling the result for bit shifted)
- Rd specifies to which register to write the result.
bit31
is set to1
to perform an addition on 64 bit operands
The instruction has fixed length of 32 bit.
< operation > < destination register > < source 1 register > < source 2 register >
ADD X1, X2, X3

x86
x86 encodes even more options inside the instruction. The size of the instruction depends on the options.
A 64 bit addition in x86 assembly may look like the following:
add rax, rdx
A straight forward translation into hexadecimal machine code may look like this:
0x48 0x01 0xd0
Prefix Opcode ModR/M
The instruction consists of tree parts: the prefix, the opcode and the ModR/M addressing mode byte.
Opcode
Opcode 0x01
indicates that the operands are not 8 bit but, 16 bit or 32 bit in size. This awkward encoding has its roots in the long history of x86 that builds upon 8 bit processors. Opcodes may be 1, 2 or 3 byte long.
Prefix REX.W
In 2003 AMD extended x86 from 32 to 64 bit. To signal to the decoder that the instruction is to be treated as 64 bit instruction, they added a prefix in front of it. Without it, the decoder treats it as regular 32 bit instruction. That is one example of the origin of variable length instructions.
For comparison, the machine code for a 32 bit addition would look the following:
add eax, edx
; 0x01 0xd0
Notice how the prefix is missing and the naming of registers has changed.
ModR/M Byte
The ModR/M byte is also known as the addressing form specifier. In our example it encodes the direct register addressing mode as well as operand registers rax
and rdx
.
It is used for:
- specifying which of the 24 possible addressing modes is to be used (depending on the addressing mode an additional addressing byte may be inserted)
- which operands are registers and which are memory addresses
- it can extend the opcode, the purpose is than specified by the primary opcode field (see Intel Developer Manual 2.1.3)
Further Details:
x86 Intel Developers Manual: https://www.intel.com/content/www/us/en/content-details/782158/intel-64-and-ia-32-architectures-software-developer-s-manual-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4.html
x86 Instruction Encoding: https://wiki.osdev.org/X86-64_Instruction_Encoding
ARM: A-Profile A64 Architecture Documentation: https://developer.arm.com/documentation/ddi0602/2023-12/Base-Instructions/ADD--extended-register---Add--extended-register--?lang=en
RISC-V technical specifications: https://wiki.riscv.org/display/HOME/RISC-V+Technical+Specifications