# Demystifying Instruction Set Architecture

If you've ever wondered what happens between writing `x = a + b` in C and your processor actually computing that sum, you're about to find out. This guide breaks down Instruction Set Architecture (ISA) — the critical interface between software and hardware that makes modern computing possible.

By the end of this post, you'll understand how instructions are categorized, encoded into binary, decoded by the processor, and how different design philosophies (RISC vs CISC) shape the devices you use every day.

---

## What is an Instruction Set Architecture?

Think of the ISA as a **contract** between hardware designers and software developers. It defines:

* What operations the processor can perform
    
* How data is accessed and manipulated
    
* The format of machine instructions
    
* The registers available to programmers
    

When Intel designs a new processor and a compiler team at GCC writes code to target it, they're both working from the same ISA specification. The ISA is what makes your compiled program run on any x86 processor, whether it's from 2010 or 2024.

**Real-world analogy:** The ISA is like a restaurant menu. The menu (ISA) tells you what dishes (instructions) are available. The kitchen (microarchitecture) decides *how* to prepare those dishes efficiently. Different restaurants might have the same menu but different kitchen layouts — just like how Intel and AMD both implement x86 but with different internal designs.

---

## Types of Instructions

Every processor supports several categories of instructions. Understanding these categories helps you see the full picture of what a CPU can do.

### 1\. Data Transfer Instructions

These move data between locations — registers, memory, and I/O devices.

| Instruction | Description | Example |
| --- | --- | --- |
| **LOAD** | Memory → Register | `LDR R1, [R2]` — Load value from memory address in R2 into R1 |
| **STORE** | Register → Memory | `STR R1, [R2]` — Store R1's value to memory address in R2 |
| **MOVE** | Register → Register | `MOV R1, R2` — Copy R2's value into R1 |
| **PUSH/POP** | Stack operations | `PUSH R1` — Save R1 onto the stack |

**Why this matters:** Every time you declare a variable, access an array element, or call a function, data transfer instructions are working behind the scenes. When you write `int x = arr[5]`, a LOAD instruction fetches that value from memory.

**Real-world connection:** In ARM processors (used in every smartphone), data transfer is so fundamental that ARM is classified as a "load-store architecture" — arithmetic can *only* happen on register values, never directly on memory.

### 2\. Arithmetic and Logical Instructions

These perform computations on data.

**Arithmetic operations:**

* `ADD R1, R2, R3` — R1 = R2 + R3
    
* `SUB R1, R2, R3` — R1 = R2 - R3
    
* `MUL R1, R2, R3` — R1 = R2 × R3
    
* `DIV R1, R2, R3` — R1 = R2 ÷ R3
    

**Logical operations:**

* `AND R1, R2, R3` — Bitwise AND
    
* `OR R1, R2, R3` — Bitwise OR
    
* `XOR R1, R2, R3` — Bitwise exclusive OR
    
* `NOT R1, R2` — Bitwise complement
    

**Shift operations:**

* `LSL R1, R2, #4` — Logical shift left (multiply by 2^4)
    
* `LSR R1, R2, #4` — Logical shift right (unsigned divide by 2^4)
    
* `ASR R1, R2, #4` — Arithmetic shift right (signed divide, preserves sign)
    

**Real-world connection:** Graphics processing relies heavily on these. When your GPU applies a color filter, it's performing thousands of AND/OR operations per pixel. When you adjust brightness, it's doing arithmetic on RGB values. The shift operations are particularly clever — `LSL #3` multiplies by 8 instantly, which is why programmers use bit shifts for performance-critical code.

### 3\. Control Flow Instructions

These change the sequence of execution.

**Unconditional branch:**

```plaintext
B label      ; Always jump to 'label'
```

**Conditional branches:**

```plaintext
BEQ label    ; Branch if Equal (zero flag set)
BNE label    ; Branch if Not Equal
BGT label    ; Branch if Greater Than
BLT label    ; Branch if Less Than
BGE label    ; Branch if Greater or Equal
BLE label    ; Branch if Less or Equal
```

**Subroutine calls:**

```plaintext
BL function  ; Branch and Link — saves return address, then jumps
RET          ; Return to saved address
```

**How conditionals work:** Before a conditional branch, you typically execute a compare instruction:

```plaintext
CMP R1, R2   ; Compare R1 and R2 (actually computes R1 - R2)
BEQ equal    ; Branch if they were equal (result was zero)
```

The CMP instruction sets **condition flags** (Zero, Negative, Carry, Overflow) that the branch instruction then checks.

**Real-world connection:** Every `if` statement, `for` loop, and `while` loop in your high-level code compiles down to these branches. Function calls use `BL` (Branch and Link), which is why debugging tools can show you a "call stack" — it's literally a stack of return addresses saved by successive BL instructions.

### 4\. Special Instructions

**System calls:**

```plaintext
SVC #0       ; Supervisor Call — request OS service
```

When your program calls `printf()` or opens a file, it eventually executes an SVC (or `syscall` on x86). This traps into the operating system kernel, which has the privileges to actually interact with hardware.

**Synchronization (for multi-core processors):**

```plaintext
LDREX R1, [R2]   ; Load Exclusive
STREX R0, R1, [R2] ; Store Exclusive — fails if another core touched the address
```

**Real-world connection:** These exclusive instructions are how your multi-threaded programs avoid race conditions. When two threads try to increment the same counter, LDREX/STREX ensure only one succeeds at a time.

---

## Instruction Encoding: How Instructions Become Binary

When you write assembly like `ADD R1, R2, R3`, the assembler must convert this into a binary pattern the processor understands. This encoding has several components.

### The Anatomy of a Machine Instruction

Every instruction contains:

1. **Opcode (Operation Code):** Identifies *what* operation to perform
    
2. **Operands:** Specify *where* to find data and *where* to put results
    
3. **Modifiers:** Additional information (shift amounts, condition codes, etc.)
    

### A Concrete Example: ARM 32-bit Encoding

Let's encode `ADD R1, R2, R3`:

```plaintext
31  28 27 26 25 24  21 20 19  16 15  12 11         0
[Cond] [0  0] [I] [Opcode] [S] [Rn  ] [Rd  ] [Operand2 ]
  4      2    1      4      1    4      4       12
```

For `ADD R1, R2, R3` with condition "always execute":

| Field | Bits | Value | Meaning |
| --- | --- | --- | --- |
| Cond | 31-28 | 1110 | Always execute (AL) |
| 00 | 27-26 | 00 | Data processing instruction class |
| I | 25 | 0 | Operand2 is a register (not immediate) |
| Opcode | 24-21 | 0100 | ADD operation |
| S | 20 | 0 | Don't update condition flags |
| Rn | 19-16 | 0010 | First source: R2 |
| Rd | 15-12 | 0001 | Destination: R1 |
| Operand2 | 11-0 | 000000000011 | Second source: R3 |

**Final encoding:** `E0821003` in hexadecimal

### Operand Types

Instructions can get their data from different sources:

**Register operands:** The data is in a CPU register.

```plaintext
ADD R1, R2, R3   ; All operands are registers
```

Registers are fast (single clock cycle access) but limited in number (typically 16-32 general-purpose registers).

**Immediate operands:** The data is embedded directly in the instruction.

```plaintext
ADD R1, R2, #100  ; Add the constant 100 to R2
```

The `#100` is encoded right into the instruction's binary. This is fast because there's no memory access, but the value's size is limited by available bits.

**Memory operands:** The data must be fetched from RAM (primarily in CISC architectures).

```plaintext
ADD EAX, [memory_address]  ; x86 can do this
```

### The Immediate Value Problem

If an instruction is 32 bits total and you need bits for the opcode, registers, etc., how many bits remain for immediate values?

In ARM's data processing format, only 12 bits are available for immediates. But 12 bits can only represent values 0-4095. What if you need larger constants?

**ARM's clever solution:** Those 12 bits are split into:

* 8 bits for a value (0-255)
    
* 4 bits for a rotation amount (rotated right by 2× this value)
    

This means you can represent:

* Any value 0-255
    
* Powers of 2 up to 2³¹
    
* Many "spread out" bit patterns like 0xFF000000
    

**What if you need an arbitrary 32-bit constant?** You have options:

1. Load it from a "literal pool" in memory
    
2. Use multiple instructions: `MOV R1, #0x1234` then `ORR R1, R1, #0x5678, LSL #16`
    
3. Some ISAs have special "load wide" instructions
    

---

## Sign Extension vs Zero Extension

When you have a small value (say, 8 bits) that needs to go into a larger container (32 bits), how do you fill the extra bits?

### Zero Extension

Fill the upper bits with zeros. Used for **unsigned** values.

**Example:** Extending an 8-bit value to 32 bits:

```plaintext
Original (8-bit):   0x7F = 0111 1111 = 127
Zero-extended (32-bit): 0x0000007F = 127 ✓
```

```plaintext
Original (8-bit):   0xFF = 1111 1111 = 255 (as unsigned)
Zero-extended (32-bit): 0x000000FF = 255 ✓
```

### Sign Extension

Copy the **sign bit** (most significant bit) into all upper positions. Used for **signed** values.

**Example:** Extending a signed 8-bit value:

```plaintext
Original (8-bit):   0x7F = 0111 1111 = +127
Sign-extended (32-bit): 0x0000007F = +127 ✓
```

```plaintext
Original (8-bit):   0xFF = 1111 1111 = -1 (in two's complement)
Sign-extended (32-bit): 0xFFFFFFFF = -1 ✓
```

If we had zero-extended that -1:

```plaintext
Original (8-bit):   0xFF = -1
WRONG zero-extend:  0x000000FF = 255 ✗  (completely wrong!)
```

### When Each Is Used

| Scenario | Extension Type |
| --- | --- |
| `unsigned char` → `unsigned int` | Zero extension |
| `char` (signed) → `int` | Sign extension |
| Memory address calculation | Usually zero extension |
| Immediate value in arithmetic | Usually sign extension |

**Real-world connection:** This is why C has both `char` (often signed) and `unsigned char`. When you write:

```c
char c = -1;
int i = c;    // Sign extension: i = -1
```

vs.

```c
unsigned char c = 255;
int i = c;    // Zero extension: i = 255
```

The compiler generates different extension instructions based on the type.

### In Instruction Encoding

Many ISAs use sign extension for immediate values in arithmetic instructions. If you have a 12-bit immediate field:

```plaintext
IMM = 0xFFF = 1111 1111 1111
```

As a signed value, this is -1. Sign-extended to 32 bits:

```plaintext
0xFFFFFFFF = -1
```

This allows instructions like `ADD R1, R2, #-1` to work with small immediate fields.

---

## Instruction Decoding: How the Processor Interprets Binary

Decoding is the reverse of encoding — the processor reads a binary instruction and figures out what to do.

### The Decode Stage

In a pipelined processor, decoding happens in its own stage:

```plaintext
Fetch → Decode → Execute → Memory → Writeback
```

During decode, the processor:

1. **Extracts the opcode:** Determines which operation
    
2. **Identifies operands:** Which registers? Immediate value?
    
3. **Sets up control signals:** Tells the ALU what operation, tells multiplexers which data paths to use
    
4. **Handles hazards:** Checks if operands are still being computed by earlier instructions
    

### Fixed-Length vs Variable-Length Decoding

**Fixed-length instructions (RISC style):**

* All instructions are the same size (e.g., 32 bits)
    
* Opcode always in the same bit positions
    
* Decoding is simple and fast
    
* Examples: ARM, MIPS, RISC-V
    

```plaintext
Simple decode logic:
opcode = instruction[31:26]  // Always these bits
```

**Variable-length instructions (CISC style):**

* Instructions range from 1 to 15+ bytes (x86)
    
* Must decode first byte(s) to determine instruction length
    
* Decoding is complex and multi-step
    
* Examples: x86, VAX
    

```plaintext
Complex decode:
1. Read first byte — is this a prefix? An opcode? 
2. Maybe read second byte for extended opcode
3. Determine length, read remaining bytes
4. Parse ModR/M byte, SIB byte, displacement, immediate...
```

**Real-world impact:** x86 processors have massive "frontend" decode units. Intel's chips actually translate x86 instructions into simpler internal "micro-ops" (μops) that look more like RISC instructions. This translation is a significant source of power consumption and complexity.

### Decode Example: ARM

For the instruction `E0821003` we encoded earlier:

```plaintext
E    0    8    2    1    0    0    3
1110 0000 1000 0010 0001 0000 0000 0011

Decoder extracts:
- Bits 27-26 = 00 → Data processing instruction
- Bits 24-21 = 0100 → ADD
- Bit 20 = 0 → Don't set flags
- Bits 19-16 = 0010 → Rn = R2
- Bits 15-12 = 0001 → Rd = R1
- Bit 25 = 0 → Operand2 is a register
- Bits 3-0 = 0011 → Rm = R3
```

The decoder then generates control signals:

* ALU operation: ADD
    
* Read registers R2 and R3
    
* Write result to R1
    
* Don't update CPSR flags
    

---

## Addressing Modes: Finding Your Data

Addressing modes specify *how* to locate operands. Different modes provide flexibility for various programming patterns.

### 1\. Immediate Addressing

The operand is the value itself, encoded in the instruction.

```plaintext
MOV R1, #42      ; R1 ← 42
ADD R2, R3, #10  ; R2 ← R3 + 10
```

**Effective address:** N/A (no memory access)

**Use case:** Constants, loop counters, known offsets

**Real-world example:** When you write `for (int i = 0; i < 100; i++)`, the `0` and `100` are immediate values.

### 2\. Register Addressing

The operand is in a register.

```plaintext
ADD R1, R2, R3   ; R1 ← R2 + R3
```

**Effective address:** N/A (registers aren't memory-addressed)

**Use case:** Fast operations on values already loaded into registers

**Real-world example:** Local variables in optimized code are often kept in registers throughout a function.

### 3\. Direct (Absolute) Addressing

The instruction contains the memory address itself.

```plaintext
LDR R1, =0x1000  ; R1 ← Memory[0x1000]
```

**Effective address:** The literal address in the instruction

**Use case:** Global variables, memory-mapped hardware registers

**Real-world example:** Accessing a GPIO control register at a fixed hardware address:

```plaintext
LDR R1, =0x40020014  ; Load GPIO port D output register address
```

### 4\. Register Indirect Addressing

A register holds the memory address.

```plaintext
LDR R1, [R2]     ; R1 ← Memory[R2]
```

**Effective address:** Contents of R2

**Use case:** Pointers, dynamic data structures

**Real-world example:** Following a linked list:

```c
// C code: current = current->next;
LDR R0, [R0]     ; R0 contains pointer, load what it points to
```

### 5\. Base + Offset Addressing

Address = base register + constant offset.

```plaintext
LDR R1, [R2, #8]   ; R1 ← Memory[R2 + 8]
```

**Effective address:** R2 + 8

**Use case:** Struct field access, stack variables

**Real-world example:** Accessing struct members:

```c
struct Point { int x; int y; int z; };
// Access point.y where R2 points to a Point:
LDR R1, [R2, #4]   ; y is 4 bytes after x
```

### 6\. Indexed Addressing

Address = base register + index register (optionally scaled).

```plaintext
LDR R1, [R2, R3]        ; R1 ← Memory[R2 + R3]
LDR R1, [R2, R3, LSL #2] ; R1 ← Memory[R2 + R3*4]
```

**Effective address:** R2 + (R3 × scale)

**Use case:** Array access with variable index

**Real-world example:** Array indexing:

```c
int arr[100];
int x = arr[i];
// R2 = base of arr, R3 = i
LDR R1, [R2, R3, LSL #2]  ; LSL #2 multiplies index by 4 (sizeof int)
```

### 7\. Pre-Indexed with Writeback

Access memory AND update the base register.

```plaintext
LDR R1, [R2, #4]!  ; R1 ← Memory[R2 + 4], then R2 ← R2 + 4
```

**Use case:** Walking through arrays, stack operations

**Real-world example:** Iterating through an array:

```c
while (*ptr != 0) { process(*ptr); ptr++; }
// Efficiently combines load and pointer increment
```

### 8\. Post-Indexed

Access memory at base, THEN update base.

```plaintext
LDR R1, [R2], #4   ; R1 ← Memory[R2], then R2 ← R2 + 4
```

**Use case:** Reading sequential memory (like popping from a stack)

**Real-world example:** The classic POP operation:

```plaintext
LDR R1, [SP], #4   ; Load from stack pointer, then increment SP
```

### 9\. PC-Relative Addressing

Address = Program Counter + offset.

```plaintext
LDR R1, [PC, #100]   ; Load from 100 bytes ahead of current instruction
B label              ; Branch to PC + offset_to_label
```

**Use case:** Position-independent code, accessing nearby constants

**Real-world example:** When you compile with `-fPIC` (position-independent code) for shared libraries, all data access uses PC-relative addressing. This allows the library to be loaded at any address.

### Addressing Mode Summary Table

| Mode | Syntax Example | Effective Address | Speed | Use Case |
| --- | --- | --- | --- | --- |
| Immediate | `#42` | N/A | Fastest | Constants |
| Register | `R3` | N/A | Fastest | Variables in registers |
| Direct | `[0x1000]` | 0x1000 | Fast | Global variables |
| Register Indirect | `[R2]` | R2 | Fast | Pointers |
| Base + Offset | `[R2, #8]` | R2 + 8 | Fast | Struct fields |
| Indexed | `[R2, R3]` | R2 + R3 | Fast | Arrays |
| Scaled Indexed | `[R2, R3, LSL #2]` | R2 + R3×4 | Medium | Array of ints/floats |
| Pre-indexed | `[R2, #4]!` | R2 + 4 (R2 updated) | Medium | Sequential access |
| Post-indexed | `[R2], #4` | R2 (then R2 updated) | Medium | Stack operations |
| PC-relative | `[PC, #100]` | PC + 100 | Fast | Position-independent code |

---

## RISC vs CISC: Two Philosophies of Processor Design

This is one of the most important architectural debates in computer history, and its effects are still visible in every device you use.

### CISC: Complex Instruction Set Computer

**Philosophy:** Provide powerful, high-level instructions that do a lot of work each.

**Characteristics:**

* Variable-length instructions (x86: 1-15 bytes)
    
* Instructions can access memory directly during computation
    
* Complex addressing modes
    
* Many specialized instructions
    
* Microcode implementation (instructions translated to simpler micro-operations)
    

**Example ISAs:** x86 (Intel/AMD), VAX, Motorola 68000

**Historical context:** In the 1960s-70s, memory was expensive and slow. Compiler technology was primitive. The solution? Make each instruction do more work, so programs need fewer instructions (less memory) and programmers could write assembly more easily.

**x86 example — String copy in one instruction:**

```plaintext
REP MOVSB    ; Repeat move string byte
             ; Copies ECX bytes from [ESI] to [EDI]
             ; Automatically increments pointers
             ; All in one instruction!
```

**The hidden complexity:** That single `REP MOVSB` instruction might take dozens of clock cycles and is internally translated into many micro-operations.

### RISC: Reduced Instruction Set Computer

**Philosophy:** Simple instructions that execute fast; let the compiler combine them.

**Characteristics:**

* Fixed-length instructions (typically 32 bits)
    
* Load-store architecture: only load/store access memory; arithmetic uses registers only
    
* Simple addressing modes
    
* Large register file (32+ registers common)
    
* Most instructions complete in one cycle
    
* Hardwired control (no microcode)
    

**Example ISAs:** ARM, MIPS, RISC-V, PowerPC, SPARC

**Historical context:** By the 1980s, compilers improved dramatically, and researchers noticed that even CISC programs mostly used simple instructions. Why have complex instructions if they're rarely used?

**ARM example — Same string copy:**

```plaintext
loop:
    LDRB R2, [R0], #1   ; Load byte, increment source pointer
    STRB R2, [R1], #1   ; Store byte, increment dest pointer
    SUBS R3, R3, #1     ; Decrement counter, set flags
    BNE loop            ; If not zero, continue
```

More instructions, but each is fast, predictable, and easy to pipeline.

### Direct Comparison

| Aspect | CISC (x86) | RISC (ARM) |
| --- | --- | --- |
| Instruction length | 1-15 bytes | 4 bytes (fixed) |
| Instructions per task | Fewer | More |
| Cycles per instruction | Variable (1-100+) | Usually 1 |
| Memory access | Any instruction | Load/Store only |
| Addressing modes | Many, complex | Few, simple |
| Register count | 8-16 general purpose | 16-32 general purpose |
| Decode complexity | Very high | Low |
| Code size | Smaller | Larger |
| Power consumption | Higher | Lower |
| Pipelining ease | Difficult | Easy |

### Real-World Impact

**In your laptop (x86):** Intel and AMD use CISC externally but RISC internally. Your x86 instructions are translated by the CPU into "micro-ops" that look like RISC instructions. This gives backward compatibility with decades of x86 software while enabling modern high-performance techniques.

**In your phone (ARM):** ARM's RISC design is why your phone gets 10+ hours of battery life despite having a powerful processor. Simple instructions mean simpler hardware, which means less power. Apple's M1/M2/M3 chips prove RISC can match or beat x86 performance while using far less energy.

**In your router/IoT devices (MIPS/ARM/RISC-V):** These small devices need extreme efficiency. RISC architectures dominate here because they can be implemented in tiny, low-power chips.

### The Modern Reality: Convergence

Today's processors blend both approaches:

**x86 evolved:** Modern Intel/AMD chips:

* Decode x86 to RISC-like micro-ops
    
* Use massive out-of-order execution engines
    
* Have many RISC-like characteristics internally
    

**ARM evolved:** Modern ARM chips:

* Added more complex instructions where beneficial
    
* Thumb-2 mode uses variable-length 16/32-bit instructions for code density
    
* Server chips (like AWS Graviton) rival x86 in raw performance
    

**The winner?** Both survived. x86 dominates desktops/servers for compatibility. ARM dominates mobile/embedded for efficiency. RISC-V is emerging as an open-source alternative. The "pure" RISC vs CISC debate is now less relevant than specific implementation quality.

### Code Density Example

**Task:** Add two 32-bit numbers from memory, store result

**x86 (CISC):**

```plaintext
ADD EAX, [EBX]     ; 2 bytes - add memory to register directly
MOV [ECX], EAX     ; 2 bytes - store result
; Total: 4 bytes
```

**ARM (RISC):**

```plaintext
LDR R1, [R2]       ; 4 bytes - load first operand
ADD R0, R0, R1     ; 4 bytes - add registers
STR R0, [R3]       ; 4 bytes - store result
; Total: 12 bytes (but we needed to load R0 first too!)
```

CISC wins on code size. But those 4 x86 bytes might take 5+ cycles, while ARM's 12 bytes take exactly 3 cycles (with proper pipelining).

---

## Putting It All Together: From C to Execution

Let's trace a simple C statement through everything we've learned:

```c
int a = 5;
int b = 3;
int c = a + b;
```

### Step 1: Compilation

The compiler translates this to assembly (ARM example):

```plaintext
MOV R0, #5        ; a = 5 (immediate addressing)
MOV R1, #3        ; b = 3 (immediate addressing)
ADD R2, R0, R1    ; c = a + b (register addressing)
```

### Step 2: Assembly

The assembler encodes these as binary:

```plaintext
MOV R0, #5  → E3A00005
MOV R1, #3  → E3A01003
ADD R2, R0, R1 → E0802001
```

### Step 3: Loading

The OS loads these bytes into memory at some address (say, 0x8000):

```plaintext
0x8000: E3A00005
0x8004: E3A01003
0x8008: E0802001
```

### Step 4: Fetch

The CPU fetches the first instruction:

* PC = 0x8000
    
* CPU reads 4 bytes from memory: E3A00005
    
* PC incremented to 0x8004
    

### Step 5: Decode

The decode unit parses E3A00005:

```plaintext
1110 0011 1010 0000 0000 0000 0000 0101

Condition: 1110 = Always
Opcode type: 001 = Data processing, immediate
Opcode: 1101 = MOV
Destination: 0000 = R0
Immediate: rotated value = 5
```

Control signals generated: Write to R0, value is immediate 5.

### Step 6: Execute

The execute unit:

* Passes 5 through the ALU (or bypass)
    
* Writes 5 to R0
    

### Steps 7-12: Repeat for remaining instructions

Each instruction goes through fetch-decode-execute. The ADD instruction reads R0 and R1, adds them in the ALU, writes result to R2.

### The Final Result

After execution:

* R0 = 5
    
* R1 = 3
    
* R2 = 8
    

If this were a real program, subsequent instructions might store R2 to memory where variable `c` lives.

---

## Key Points

1. **ISA is the contract** between hardware and software — it defines what a processor can do and how to ask it.
    
2. **Instructions are categorized** into data transfer, arithmetic/logical, control flow, and special types — together they enable all computation.
    
3. **Encoding packs instructions into binary** using opcodes, register specifiers, and immediate values — clever encoding allows more functionality in limited bits.
    
4. **Sign extension preserves negative numbers** when widening; zero extension is for unsigned values — getting this wrong causes subtle bugs.
    
5. **Decoding extracts meaning from binary** — fixed-length (RISC) is simpler than variable-length (CISC).
    
6. **Addressing modes provide flexibility** for accessing data in different ways — from immediate constants to complex indexed array access.
    
7. **RISC emphasizes simplicity and speed** per instruction; CISC emphasizes power per instruction — modern processors blend both ideas.
    
8. **The real world is messy** — x86 is CISC outside, RISC inside; ARM adds complexity where it helps; RISC-V is the new kid finding its niche.
