Assembly Nerds
As of now, we have covered different aspects of the CPU in general. Now we shift gears a little bit and go more specific to assembly language. But before jumping in, let me clear things up with a quick context of how a program works in general.
A program takes inputs (either hard-coded or provided by the user) and then the CPU processes those inputs to generate some kind of output. That output doesn’t just float in the air — it has to be stored somewhere. Depending on what we want, it can either be stored in the memory or directly in the registers.

As you know, the CPU is very fast, but it’s also very picky about some things. It always wants to know exactly where the data is coming from, where it should go, and what size the data is. That means whenever we write instructions, we must be clear about whether the value is:
A constant number we are directly giving to the CPU,
A value sitting inside a register,
Something stored in memory at a specific location,
Or how much size is required to store the output.
The way of telling the CPU “where the data lives” is what we call addressing (as we discussed before). However, in assembly language, there are specific rules for how we can do that. These rules are known as addressing modes.
So, before we start writing bigger assembly programs, we first need to understand these addressing modes, because every single instruction (mov, add, loop, etc.) depends on them to know where to fetch data from, where to store it, and how much size is required for it.
Addressing Modes
Addressing modes are the rules that restrict us (developers) by defining the correct way to access memory or registers. For example:
Can we store more than 20 bits of data in the
AXregister? (No.)Can we access any random memory location in RAM without restriction? (Also no.)
These rules are what make assembly both strict and predictable.
In 16-bit assembly programming, there are 8 addressing modes, which are the following:
Immediate Mode
Direct Access Mode
Base Register Indirect Mode
Base Register Indirect + Offset Mode
Base Register + Index Mode
Base + Index + Offset Indirect Mode
Index Register Indirect Mode
Index Register Indirect + Offset Mode
We will learn about each of them shortly. Trust me — their meanings are much simpler than how complicated they look at first glance.
The above addressing modes tell us which instruction are allowed, but why not just first know which instruction are not allowed.
Not Allowed Methods
1. Size Mismatch
We are not allowed to store data in a register that is larger or smaller than the register can hold. For example,
AXis a 16-bit register. That means we cannot move just 8-bit data directly intoAX. For 8-bit data, we must use either its lower (AL) or higher (AH) part. Similarly, we cannot add a 16-bit value to an 8-bit register.Example: Not Allowed
mov ax, bl mov al, ax mov bh, cx mov cl, dx mov cx, dlExample: Allowed
mov ax, bx mov cx, dx mov al, bh mov cl, dl2. Memory To Memory
We are not allowed to directly move data from one memory cell to another. For example, if we have two variables in memory, we cannot tell the CPU: “Take that variable from memory, add it to another variable in memory, and store the result back into a new memory cell.”
Why? Because (as we discussed in the system buses section) every time the CPU fetches data from memory, it has to go through the address bus, control bus, and data bus. If we tried to do memory-to-memory operations for everything, it would be extremely slow and costly.
That’s why we have registers. If we need to work with memory data, we first move it into a register, perform our operations there (which is very fast since registers don’t require buses), and then move the result back into memory if needed.
Example: Not Allowed
mov var1, var2 ; Very slow and not allowedExample: Allowed
mov ax, var1 ; Load value from memory into register mov var2, ax ; Store it back into memory mov ax, bx ; Fastest way (no buses needed)3. Offset Subtraction
First of all, an offset is just a number that represents the distance between memory addresses. You’ll see this more clearly when we work with arrays later, but let me give you a quick idea here.
Suppose we have 10 consecutive memory locations holding related data. If we want to access the data in the 7th cell, how do we do it? Simple: if the first memory location is
0x00124, then the 7th cell can be accessed as:0x00124 + 7Easy, right?
But here’s the catch: while we can add offsets, we are not allowed to subtract them. For example:
0x00124 - 7This is not allowed. We can only add offsets, not subtract. The number we add (like
7in the example) is what we call the offset.
So all the above not allowed methods are defined by the architecture of the processor, and based on that we can use different addressing modes. The 8 addressing modes that are allowed which previously I was discussing (Sorry for being sidetracked..).
Why did I first explain the “not allowed” things? Because they’re easier to understand — and now that you’ve seen them, it will be much simpler to explain the 8 allowed addressing modes of assembly.
1. Immediate Mode
You know we’ve already seen this type of declaration before. Do you remember our Warmup Code from the very beginning? That’s where we moved some values directly into registers. Forgot already? No worries — here’s the snippet again:
mov ax, 6
mov bx, 9This is exactly what Immediate Mode is about. In this mode, we directly put (or move) data into a register using our instruction. The value we provide is a constant, written right in the code, and it gets copied straight into the register.
For example:
mov cx, 25
mov dx, 100Here, 25 and 100 are immediate values, and the CPU places them directly inside the registers CX and DX.
2. Direct Access Mode
In this mode, we give a direct memory address or a variable name. (Yes, a variable is basically just a direct address — even though we give a memory cell a name, behind the scenes, it’s just an address. The assembler handles that translation for us.)
For example:
mov ax, var1
mov ax, [var1]
mov ax, 0x000124
mov ax, [0x000124]This mode is called Direct Access because we are explicitly telling the CPU exactly which memory location to use.
3. Base Register Indirect Mode
Remember the BX register we learned about in the General Purpose Registers section? And in the previous section, we also learned about the meaning of the [] brackets. In this mode, the idea is simple: combine a base register like BX , BPwith the indirect mode [] brackets.
So what does this actually mean? We can use BX together with [] to access memory indirectly. You might be thinking: “Wait… registers don’t have addresses, so how can we use [] with a register?”
You’re right, registers themselves don’t have addresses. But remember the purpose of the BX register: it is often used to store a base pointer, which is essentially the address of some memory location. So when we write [BX], we’re telling the CPU:
“Go to the memory address stored inside BX and get the value there.”
Example:
mov bx, var1 ; Store the address of var1 in BX
mov [bx], ax ; Store the value of AX into the memory address pointed by BX
mov ax, [bx] ; Load the value stored at the address in BX into AXSo in short, Base Register Indirect Mode lets us access memory through a pointer stored in a register. I hope that makes sense.
4. Base Register Indirect + Offset Mode
We’ve already talked about offsets and Base Register Indirect Mode. Now, this mode is simply a combination of both. One thing to note is that we often use an index register to store the offset. Remember the index registers SI and DI that we discussed earlier? They come in handy here.
Here’s how it looks:
mov ax, [bx + 2] ; Load the value at memory address (BX + 2) into AX
mov [bx + 2], ax ; Store the value of AX into memory address (BX + 2)We use these kinds of declarations a lot when working with arrays, where the offset helps us access different elements.
For now, just focus on understanding the concept: we are using a base register to point to a starting memory address and then adding an offset (either a number or an index register) to reach the exact location we want.
5. Base Register + Index Mode
The mode is exactly same as before, there we also use both a base register and an index register together to calculate the memory address. The base register usually points to the start of a block of memory, while the index register helps us move around inside that block.
Example:
mov ax, [bx + si] ; Load value from memory address (BX + SI) into AX
mov [bx + di], ax ; Store value of AX into memory address (BX + DI)This is very similar to the previous mode you can see, but here the offset is always coming from an index register, not just a constant number. Just a little distinction other wise the same.
6. Base + Index + Offset Indirect Mode
This is basically the supercharged version of all the previous modes. Here, we combine:
A base register (like
BX)An index register (like
SIorDI)A constant offset
All three together give us the exact memory address we want to access. This mode is very powerful when working with arrays of structures or tables, where the starting point is a base, we move around with an index, and sometimes add a fixed offset to reach a specific element or field.
Example:
mov ax, [bx + si + 4] ; Load value from memory address (BX + SI + 4) into AX
mov [bx + di + 2], ax ; Store value of AX into memory address (BX + DI + 2)Here’s what’s happening step by step:
BXpoints to the start of a block (base address).SIorDImoves us to a specific element inside that block (index).The
+ 4or+ 2adds a fixed offset to reach the exact field or location we want.
7. Index Register Indirect Mode
Here, we drop the base register and just use an index register like SI or DI to point directly to a memory location. The value inside the index register acts as the address itself.
Example:
mov ax, [si] ; Load value from memory address stored in SI into AX
mov [di], ax ; Store value of AX into memory address stored in DISo instead of combining with a base register, we rely purely on the index register. It’s simpler but still very powerful when we work with arrays or blocks of data.
8. Index Register Indirect + Offset Mode
Finally, this mode combines an index register with an offset, similar to how we combined a base register with an offset before.
Example:
mov ax, [si + 4] ; Load value from memory address (SI + 4) into AX
mov [di + 2], ax ; Store value of AX into memory address (DI + 2)This is particularly useful when you want to access a specific element in an array while the index register already points to the start of the array.
For now, the key idea is simple:
We can calculate the exact memory location by combining a register (base or index) with an optional offset.
Addressing Modes Summary Table
Immediate Mode
Constant value
mov ax, 6
Value is hard-coded; goes directly into register. Cannot change at runtime.
Direct Access Mode
Memory address / variable
mov ax, [var1]
Access memory directly. [] means fetch value at the address; without [] gives the address itself.
Base Register Indirect Mode
Memory via base register
mov ax, [bx]
Base register (BX/BP) holds address; [] fetches/stores value at that address.
Base Register Indirect + Offset Mode
Base register + numeric offset
mov ax, [bx + 2]
Access memory at base + offset. Useful for arrays.
Base + Index Mode
Base + index register
mov ax, [bx + si]
Base points to start; index moves within block.
Base + Index + Offset Mode
Base + index + constant
mov ax, [bx + si + 4]
Combines base, index, and offset. Perfect for structured arrays or tables.
Index Register Indirect Mode
Index register only
mov ax, [si]
Index register points directly to memory location.
Index Register Indirect + Offset Mode
Index register + numeric offset
mov ax, [si + 4]
Index points to start; offset moves to exact element.
Last updated
Was this helpful?