Assembly Instructions

We covered a lot so far that built the foundation for working with assembly, but so far we haven’t learned about the different instructions that are used to write an assembly program. Remember our warmup code? We used the mov and add instructions there. In this section, we dive deeper into those instructions.

You may say: “Why now?” Well, let me give you some context. To work with assembly language, we need 2 things (there’s more in reality, but for now these two are enough):

  1. Registers & Memory

  2. Instructions

You can see we already learned about different types of registers and memory addressing modes. The only thing left was instructions—so that’s what we tackle now.

Registers and memory are where we store data, but what do we do with that data? That’s where instructions come in. Using instructions, we work with the data and perform operations to solve specific problems. I hope that makes sense?

When we talk about instructions, it’s better to address one thing early: because assembly language is a low-level representation of machine language, it is tightly coupled to the CPU design. There are many CPU vendors out there, and each of them has its own way of doing things. Because of that, there is no single universal set of assembly instructions.

Let’s suppose we are very familiar with assembly programming. Still, one day you might come across an instruction that looks completely new. Why? Because that instruction is valid only for a specific vendor’s processor. In that case, we need to check that vendor’s documentation. Don’t worry—it’s not as hard as it sounds. If you understand the basics of assembly (what we are learning now), then adapting to a new instruction set is much easier.

Now, here’s the good news: while some instructions are vendor-specific, there are common categories of instructions that exist in almost every assembly language. And these are enough for us to start writing assembly programs as beginners. These categories are:

  1. Data Transfer Instructions

  2. Arithmetic and Logic Instructions

  3. Control Flow Instructions

  4. System Control Instructions

Data Transfer Instructions

Explanation of all the data transfer instruction are written below:

  • Move Instruction (mov):

    The MOV instruction, as discussed earlier, is used to transfer data from a source to a destination.

    • Source: where the data comes from. This can be a constant (immediate value), a register, or a memory location/variable.

    • Destination: where the data goes. This can be a register or a memory location.

      Important to remember: you cannot directly move data from one memory location to another (memory-to-memory move). Data must go through a register first.

    Logical Flow

    When the CPU sees a MOV instruction, here’s what it does step by step:

    1. Reads the source operand (value, register, or memory).

    2. Copies that value into the destination operand.

    3. The source remains unchanged — only the destination gets updated.

    Syntax

    mov <Destination>, <Source>
    • <Destination> = Required. Where the value will be placed (register or memory).

    • <Source> = Required. What value is being copied (constant, register, or memory).

    Examples

    mov ax, 6        ; Move immediate value 6 into AX
    mov var1, bx     ; Move value from BX into memory location var1
    mov bx, var1     ; Move value from memory location var1 into BX
    mov ax, bx       ; Move value from ax into bx - very fast

Tip: If both operands are registers, the transfer happens very fast (since no memory or buses are involved). If one operand is memory, the CPU has to go through system buses, which makes it slower.


  • PUSH & POP

I admit I haven’t explained the stack much yet. In the stack register section we touched on this topic a little bit, and I mentioned we’d go through it later because we hadn’t yet reached the point where we work with the stack.

Right now, we are still learning the basics — and the stack is a topic of its own kind. We will cover it fully later, but for now go and revise the stack register section again, because there I covered a little bit about the stack. Here, we’ll just focus on two instructions used to work with it.

Push Instruction (PUSH)

The PUSH instruction is used to store data onto the stack.

Syntax

push <Source>
  • Source: Required. Can be a register or a memory operand.

  • Destination: Always the stack (handled internally by the CPU, not by us).

Logical Flow

  1. The Stack Pointer (SP) register decreases by 2 (in 16-bit mode).

  2. The CPU copies the source operand into the memory location now pointed to by SP.

Examples

push ax      ; Push AX register value onto stack
push bx      ; Push BX register value onto stack
push var1    ; Push the value of memory variable var1 onto stack

Pop Instruction (POP)

The POP instruction is used to retrieve data from the top of the stack and place it into a destination.

Syntax

pop <Destination>
  • Source: Always the stack (handled internally by the CPU).

  • Destination: Required. Can be a register or a memory operand where the popped value will be stored.

Logical Flow

  1. The CPU reads the value stored at the memory location currently pointed to by SP.

  2. That value is copied into the destination operand.

  3. The Stack Pointer (SP) register increases by 2 (restoring the stack to its previous state).

Examples

pop ax      ; Pop the top value from stack into AX
pop bx      ; Pop the top value from stack into BX
pop var1    ; Pop the top value from stack into memory variable var1

  • IN & OUT

Just like with the stack, I haven’t explained I/O ports yet. That’s a whole topic of its own, and we’ll study it later when we talk about input/output devices.

For now, you only need to know the instructions that allow us to transfer data between the CPU and I/O ports. Don’t stress about what a port really is right now — we’ll cover that later. Just cram the behavior of these instructions.

In Instruction (IN)

The IN instruction is used to read data from an I/O port into a register.

Syntax

in <Destination>, <Port>
  • Destination: Required. Must be AL, AX, or EAX depending on the data size.

  • Port: Required. The I/O port address, which can be an immediate value or stored in DX.

Logical Flow

  1. The CPU places the port address on the address bus.

  2. Data from that port is read and transferred into the destination register.

Examples

in al, 60h   ; Read data from port 60h into AL
in ax, dx    ; Read data from the port whose address is in DX into AX

Note: Don’t worry if I/O ports sound confusing right now. Later we’ll cover exactly what ports are and how they are used. For now, just remember that IN moves data from a port → into a register.

Out Instruction (OUT)

The OUT instruction is the opposite of IN. It is used to send data from a register to an I/O port.

Syntax

out <Port>, <Source>
  • Port: Required. The I/O port address, which can be an immediate value or stored in DX.

  • Source: Required. Must be AL, AX, or EAX depending on the data size.

Logical Flow

  1. The CPU takes the value from the source register.

  2. That value is sent to the specified port address through the data bus.

Examples

out 60h, al   ; Send the value in AL to port 60h
out dx, ax    ; Send the value in AX to the port whose address is in DX

Arithmetic and Logic Instructions

Arithmetic Instructions

These instructions perform mathematical operations on data stored in registers or memory. The CPU’s Arithmetic Logic Unit (ALU) is responsible for executing them.

We’ll look at the most common ones here.

  • Add Instruction (ADD)

    The ADD instruction adds the source operand to the destination operand and stores the result in the destination.

    Syntax

    add <Destination>, <Source>
    • Destination: Required. Can be a register or memory location where the result will be stored.

    • Source: Required. Can be an immediate value, a register, or a memory operand.

    Logical Flow

    1. The CPU reads the values of both operands.

    2. It performs addition (Destination + Source).

    3. The result is stored back into the destination operand.

    4. Flags in the FLAGS register are updated (e.g., Zero Flag, Carry Flag, Overflow Flag).

    Examples

    add ax, bx     ; AX = AX + BX
    add ax, 10     ; AX = AX + 10
    add var1, bx   ; var1 = var1 + BX

Subtract Instruction (SUB)

The SUB instruction subtracts the source operand from the destination operand and stores the result in the destination.

Syntax

sub <Destination>, <Source>
  • Destination: Required. The operand where the result will be stored.

  • Source: Required. The operand to subtract from the destination.

Logical Flow

  1. The CPU reads the values of both operands.

  2. It performs subtraction (Destination - Source).

  3. The result is stored back into the destination operand.

  4. Flags are updated accordingly (Zero Flag, Sign Flag, Carry Flag, Overflow Flag).

Examples

sub ax, bx     ; AX = AX - BX
sub ax, 5      ; AX = AX - 5
sub var1, cx   ; var1 = var1 - CX

Increment Instruction (INC)

The INC instruction increases the value of the operand by 1. Generally we use it to increase the value of cx register.

We use this inc and dec instruction very often when we deal with LOOP instruction.

Syntax

inc <Operand>
  • Operand: Required. Can be a register or memory operand.

Logical Flow

  1. The CPU reads the operand value.

  2. It adds 1 to the operand.

  3. The result is stored back in the same operand.

Note: Flags are updated (but the Carry Flag is not affected).

Examples

inc ax        ; AX = AX + 1
inc bx        ; BX = BX + 1
inc var1      ; var1 = var1 + 1

Decrement Instruction (DEC)

The DEC instruction decreases the value of the operand by 1.

Syntax

dec <Operand>
  • Operand: Required. Can be a register or memory operand.

Logical Flow

  1. The CPU reads the operand value.

  2. It subtracts 1 from the operand.

  3. The result is stored back in the same operand.

  4. Again flags are updated (but the Carry Flag is not affected).

Examples

dec ax        ; AX = AX - 1
dec bx        ; BX = BX - 1
dec var1      ; var1 = var1 - 1

Note: These arithmetic instructions are the foundation of programming in Assembly. They are simple, but later when we build loops, counters, and condition checks, you’ll see just how much ADD, SUB, INC, and DEC are used everywhere.


Logic Instructions

Logical instructions perform bitwise operations on data stored in registers or memory. These are executed by the CPU’s Arithmetic Logic Unit (ALU), just like arithmetic instructions, but instead of numbers, they operate on the individual bits of the operands.

These instructions are essential for tasks like masking bits, setting or clearing flags, and checking conditions efficiently.

  • AND Instruction (AND)

    The AND instruction performs a bitwise AND operation between the source and destination operands. The result is stored in the destination operand. Each bit in the result is 1 only if the corresponding bits in both operands are 1.

    Syntax

    and <Destination>, <Source>
    • Destination: Required. Can be a register or memory operand.

    • Source: Required. Can be an immediate value, a register, or a memory operand.

    Logical Flow

    1. The CPU reads both operands.

    2. Performs a bitwise AND (Destination & Source).

    3. Stores the result back in the destination operand.

    4. Updates flags (Zero Flag, Sign Flag, Overflow Flag cleared, Carry Flag cleared).

    Examples

    and ax, bx      ; AX = AX & BX
    and al, 0Fh     ; AL = AL & 0Fh (mask lower 4 bits)
    and var1, ax    ; var1 = var1 & AX

OR Instruction (OR)

The OR instruction performs a bitwise OR operation between the source and destination operands. Each bit in the result is 1 if either corresponding bit in the operands is 1.

Syntax

or <Destination>, <Source>

Logical Flow

  1. CPU reads both operands.

  2. Performs bitwise OR (Destination | Source).

  3. Stores the result back in the destination.

  4. Updates flags (Zero Flag, Sign Flag, Overflow Flag cleared, Carry Flag cleared).

Examples

or ax, bx       ; AX = AX | BX
or al, 80h      ; AL = AL | 80h (set the highest bit)
or var1, ax     ; var1 = var1 | AX
  • XOR Instruction (XOR)

    The XOR instruction performs a bitwise exclusive OR operation. Each bit in the result is 1 only if the corresponding bits in the operands are different.

    Syntax

    xor <Destination>, <Source>

    Logical Flow

    1. CPU reads both operands.

    2. Performs bitwise XOR (Destination ^ Source).

    3. Stores the result in the destination.

    4. Updates flags (Zero Flag, Sign Flag, Overflow Flag cleared, Carry Flag cleared).

    Examples

    xor ax, bx      ; AX = AX ^ BX
    xor al, 0FFh    ; AL = AL ^ 0FFh (flip all bits)
    xor var1, ax    ; var1 = var1 ^ AX

NOT Instruction (NOT)

The NOT instruction performs a bitwise negation (complement) on the operand. Every 0 becomes 1, and every 1 becomes 0.

Syntax

not <Operand>
  • Operand: Required. Can be a register or memory operand.

Logical Flow

  1. CPU reads the operand.

  2. Flips all bits of the operand.

  3. Stores the result back in the same operand.

  4. Flags are not affected.

Examples

not ax          ; AX = ~AX
not al          ; AL = ~AL
not var1        ; var1 = ~var1

Logical instructions are extremely useful for masking, setting, or toggling specific bits without touching the others. For example, AND is commonly used to clear bits, OR to set bits, and XOR to toggle bits. NOT is perfect when you need the complement of a value.

It make more sense when we practically work with it, Right now just try to understand there purpose.

Shift & Rotate Instruction

Shift and rotate instructions are also bitwise operations, but instead of operating on individual bits with AND/OR/XOR, they move bits left or right inside a register or memory operand. These instructions are very handy for tasks like multiplication/division by powers of 2, bit masking, and cyclic bit operations.

  • SHL / SAL Instruction (SHL / SAL)

    The SHL (Shift Left) and SAL (Shift Arithmetic Left) instructions are essentially the same — they shift all bits in the operand to the left by a specified number of positions. Bits shifted out on the left are discarded, and zeros are filled in on the right.

    Syntax

    shl <Operand>, <Count>
    sal <Operand>, <Count>
    • Operand: Required. Can be a register or memory operand.

    • Count: Required. Number of positions to shift (immediate value or CL register).

    Logical Flow

    1. CPU reads the operand value.

    2. Shifts all bits to the left by <Count> positions.

    3. Fills the vacated rightmost bits with zeros.

    4. Updates flags (Carry Flag, Zero Flag, Sign Flag, Overflow Flag depending on operation).

    Examples

    shl ax, 1       ; Shift all bits in AX one position to the left (AX * 2)
    sal bx, 3       ; Shift all bits in BX three positions to the left (BX * 8)

SHR Instruction (SHR)

The SHR (Shift Right) instruction shifts all bits in the operand to the right. Bits shifted out on the right are discarded, and zeros are filled in on the left.

Syntax

shr <Operand>, <Count>

Logical Flow

  1. CPU reads the operand.

  2. Shifts bits to the right by <Count> positions.

  3. Fills the leftmost bits with zeros.

  4. Updates flags accordingly.

Examples

shr ax, 1       ; Shift AX one bit to the right (AX / 2)
shr bx, 2       ; Shift BX two bits to the right (BX / 4)

SAR Instruction (SAR)

The SAR (Shift Arithmetic Right) instruction also shifts bits to the right, but preserves the sign bit for signed numbers (the most significant bit remains the same).

Syntax

sar <Operand>, <Count>

Logical Flow

  1. CPU reads the operand.

  2. Shifts bits right by <Count> positions.

  3. Preserves the sign bit (MSB) for signed numbers.

  4. Updates flags (Carry, Zero, Sign, Overflow).

Examples

sar ax, 1       ; Arithmetic shift right AX by 1
sar bx, 3       ; Arithmetic shift right BX by 3

ROL Instruction (ROL)

The ROL (Rotate Left) instruction rotates all bits in the operand to the left. Bits that are shifted out on the left re-enter on the right — it’s a cyclic rotation.

Syntax

rol <Operand>, <Count>

Logical Flow

  1. CPU reads the operand.

  2. Rotates bits to the left by <Count> positions.

  3. Bits shifted out from the left re-enter on the right.

  4. Updates flags (Carry Flag may be affected).

Examples

rol ax, 1       ; Rotate AX left by 1 bit
rol bl, 2       ; Rotate BL left by 2 bits

ROR Instruction (ROR)

The ROR (Rotate Right) instruction rotates all bits in the operand to the right. Bits that are shifted out on the right re-enter on the left.

Syntax

ror <Operand>, <Count>

Logical Flow

  1. CPU reads the operand.

  2. Rotates bits to the right by <Count> positions.

  3. Bits shifted out on the right re-enter on the left.

  4. Updates flags (Carry Flag may be affected).

Examples

ror ax, 1       ; Rotate AX right by 1 bit
ror bl, 2       ; Rotate BL right by 2 bits

Tip: Shift instructions are excellent for fast multiplication/division by 2, 4, 8…, while rotate instructions are very useful for cyclic bit manipulation, encryption, and hardware control. Try to understand it — they will appear often in low-level programming and bitmask tasks.

Masking

Masking is the process of applying a bit pattern (called a mask) to a register or memory value in order to selectively modify or check specific bits.

The mask itself is just a binary number, where the positions of 1s and 0s decide which bits of the original value are affected.

  • A 1 in the mask usually means keep or change a bit.

  • A 0 in the mask usually means clear or ignore a bit.

Masking is mostly done using logical instructions like AND, OR, XOR, and sometimes TEST.

The different types of masking are:

  • Selective Bit Clearing (Using AND)

We use the AND instruction with a mask to clear (set to 0) specific bits.

Assembly Example

mov al, 4Ch       ; AL = 0100 1100
and al, 0F0h      ; Mask = 1111 0000

Manual Calculation

AL     0100 1100
Mask   1111 0000
----------------
AND    0100 0000

Result: AL = 0100 0000

Selective Bit Setting (Using OR)

We use the OR instruction with a mask to set (force to 1) specific bits.

Assembly Example

mov al, 4Ch       ; AL = 0100 1100
or  al, 0Fh       ; Mask = 0000 1111

Manual Calculation

AL     0100 1100
Mask   0000 1111
----------------
OR     0100 1111

Result: AL = 0100 1111

Selective Bit Inversion (Using XOR)

We use the XOR instruction with a mask to toggle (invert) specific bits.

Assembly Example

mov al, 4Ch       ; AL = 0100 1100
xor al, 0Fh       ; Mask = 0000 1111

Manual Calculation

AL     0100 1100
Mask   0000 1111
----------------
XOR    0100 0011

Result: AL = 0100 0011

Selective Bit Testing (Using AND or TEST)

We can test specific bits by using AND with a mask. If the result is zero, those bits were not set. Otherwise, they were set.

Assembly Example

mov al, 4Ch       ; AL = 0100 1100
and al, 08h       ; Mask = 0000 1000

Manual Calculation

AL     0100 1100
Mask   0000 1000
----------------
AND    0000 1000

Result: Non-zero → the 3rd bit (from right) is set.

Alternatively, TEST can be used for the same purpose, but unlike AND, it does not change the operand.

mov al, 4Ch       ; AL = 0100 1100
test al, 08h      ; Check if 3rd bit is set

If Zero Flag (ZF) = 0 → bit is set.

If Zero Flag (ZF) = 1 → bit is not set.

Masking is therefore a systematic way of clearing, setting, toggling, or checking bits. It is one of the most common operations when dealing with low-level programming tasks.

Control Flow Instructions

Control flow instructions are what make assembly programming more dynamic. So far, we’ve only learned about data and arithmetic or logical operations. That executes instruction after instruction in a line-wise manner.

But programming is not just about straight execution — it’s about making decisions, based on decisions either rejecting or accepting some instructions, temporarily going to different instructions but later coming back, etc.

This is exactly where control flow instructions come into play.

These instructions allow us to alter the normal flow of execution and jump through different sets of instruction blocks, we call it branching in assembly.

  • Jump to another part of the program.

  • Execute code only if a certain condition is true.

  • Repeat instructions in loops.

  • Call functions and return back when done.

Let’s go step by step, and we start with Jumping instructions. There are two categories of jump instructions available in assembly language.

  1. Unconditional Jumps

  2. Conditional Jumps

Below we dive deep into each of them:


  • Unconditional Jump (JMP)

    The JMP instruction transfers execution to a different part of the program, no matter what. It doesn’t care about conditions — it just goes straight to the specified label.

    Syntax:

    jmp <Label>

Label: A label is simply the name of a different branch (or section) of instructions. Inside a label, you can place any instructions you’ve learned so far — or even ones you’ll learn later. When the JMP instruction executes, the CPU jumps to that branch and starts executing from there, instead of continuing with the next instruction in sequence.

Types of Unconditional Jumps

Unconditional jumps are divided into two main categories:

  1. Intrasegment jumps → jump inside the same code segment (only IP changes).

  2. Intersegment jumps → jump to a different code segment (both CS and IP change).

1. Intrasegment Jumps

These jumps remain inside the current code segment. They only update the Instruction Pointer (IP).

  • Short Jump

    Used when the target label is close (within -128 to +127 bytes from the current instruction).

    Opcode: EB

    Size: 2 bytes → 1 byte for opcode EB, 1 byte for displacement.

    Example:

    jmp short label1   ; Jump in (-128 to +127 range)

Note: Conditional jumps are always short

  • Near Jump

    Used when the target is within the same segment, but the range is larger than short jump allows.

    Opcode: E9

    Size: 3 bytes → 1 byte for opcode E9, 2 bytes for displacement.

    Example:

    jmp near label2    ; Jump in (-32768 to +32768 range)
    Fig 4.0: Short and Near Jump

Short and Near Jumps are also called relative jumps because they use relative addressing. That means instead of giving the full address of the label, we only provide the displacement (the distance from the current instruction to the target label). The CPU then adds this displacement to the current IP to calculate the final address on its own.

2. Intersegment Jump

These jumps transfer control to a different code segment. That means both CS (code segment register) and IP (instruction pointer) are updated.

  • Far Jump

    Opcode: EA

    Size: 5 bytes → 1 byte for opcode EA, 2 bytes for new IP, 2 bytes for new CS.

    Example:

    jmp far ptr label3   ; Jump to another code segment
Fig 4.1: Far JUMP

Examples:

jmp start       ; Jump unconditionally to label 'start'
jmp exit        ; Skip code and go directly to 'exit'
jmp short loop1 ; Example of short jump

Note: When we work with AFD debugger later, you will actually see these opcodes (EB, E9, EA) and the instruction sizes directly. That makes the theory much more clear. For now, just keep in mind how short, near, and far jumps are different.


Conditional Jumps

Conditional jumps are the real deal when writing logic. They depend on the status of flags in the FLAGS register (set by arithmetic/logical instructions).

Remember: We already studied the FLAGS register. Keep that section in mind here, because every conditional jump depends on the status of one or more flags.

Common conditional jumps:

  1. JE / JZ (Jump if Equal / Zero):

    This jump changes the flow of instructions if and only if the zero flag (ZF) is 1. That happens when the result of the previous operation (often a comparison) was zero.

    JE start    ; Jump if ZF = 1
    JZ start    ; (same as JE)
  2. JNE / JNZ (Jump if Not Equal / Not Zero):

    Works opposite to JE. It jumps if the zero flag (ZF) is 0, meaning the result of the previous operation was not zero.

    JNE loop    ; Jump if ZF = 0
    JNZ loop    ; (same as JNE)
  3. JG / JNLE (Jump if Greater):

    This jump is taken if the zero flag (ZF) is 0 and the sign flag (SF) equals the overflow flag (OF). Used for signed comparisons where the left operand is greater.

    JG greater_case    ; Jump if ZF = 0 AND SF = OF
    JNLE greater_case  ; (same as JG)
  4. JL / JNGE (Jump if Less):

    This jump is taken when the sign flag (SF) is not equal to the overflow flag (OF). Used for signed comparisons where the left operand is less.

    JL less_case    ; Jump if SF ≠ OF
    JNGE less_case  ; (same as JL)
  5. JGE / JNL (Jump if Greater or Equal):

    This jump is taken when the sign flag (SF) equals the overflow flag (OF). Used for signed comparisons where the left operand is greater or equal.

    JGE check_done    ; Jump if SF = OF
    JNL check_done    ; (same as JGE)
  6. JLE / JNG (Jump if Less or Equal):

    This jump is taken when either the zero flag (ZF) is 1 or the sign flag (SF) is not equal to the overflow flag (OF). Used for signed comparisons where the left operand is less or equal.

    JLE exit    ; Jump if ZF = 1 OR SF ≠ OF
    JNG exit    ; (same as JLE)

There are many other conditional jumps as well (i.e., JA, JB, JAE, JBE, etc.), especially for unsigned numbers.

For now, remember that they all depend on combinations of Zero Flag (ZF), Sign Flag (SF), and Carry Flag (CF).


CMP Instruction (Compare)

Above we kept mentioning comparison again and again. Now let’s talk about it.

CMP is not a jump itself, but it is always used before conditional jumps. Its acronym is Compare but it doesn’t do it like we normally do. It basically subtracts things like the SUB instruction we learned in the Arithmetic and Logic instructions section but with one difference.

It subtracts the source from the destination internally, but it doesn’t store the result back, it just updates the flags. You may say what the hell, it doesn’t make sense? So I’ll try to explain the purpose with a simple example so the concept becomes more solid.

Working Example

Let’s say we have stored 5 in AX register and we want to compare if they are equal or not with the value placed in BX, let’s say 4. What we know about the cmp instruction: it subtracts things and updates the flags.

We do that here: ax - bx5 - 4 = 1 which is not zero, right?

We know the zero flag only becomes 1 if the answer is zero, but in this case it is not. Meaning the zero flag is 0. That’s the whole logic: if our answer is zero (only when both values are the same) then the zero flag becomes 1, which tells us that both numbers are equal. If not, then other flags will get updated based on the result, and we judge things from that. I hope it makes sense?

Combine that knowledge of cmp with the definition of each conditional jump. And see if you can understand how different flags prove the logic of equal, greater, or less.

Now let’s see the syntax and examples of how compare instructions are written.

Syntax

cmp <Destination>, <Source>

Examples

cmp ax, bx    ; Compare AX with BX
je equal_case ; Jump if equal

LOOP Instruction

When solving any problem, we often come to situations where we have to keep going through the same process with different results again and again. Let’s say we are writing a code to find a factorial. As we know, we have to keep multiplying the number with its previous number until the previous number becomes one.

Now let’s engineer this problem: we know the mul instruction is used for multiplication, and we have to find 4!. What we can do is write 4 mul instructions with subtracted 1 values. However, think of it: what if the number is larger or comes from the user? Now how do we deal with it? Should we have to keep writing the same instruction one by one, again and again? Nah, I don’t know about you but if I were you then I’d take Arts instead of Computer Science. 🎨

That’s where LOOP comes as a savior of programmers. The LOOP instruction is designed specifically for iteration to solve these kinds of problems, where we have to execute the same instructions again and again without writing 100 lines.

In assembly language we uses the CX register as a counter. That counts how many iterations are complete, and if it becomes zero that tell loop cycle to get finished.

Syntax

<Label>:
    ; instructions here

loop <Label>

In the above syntax, we first define a label that tells where the loop branch starts. Later we write the actual loop instruction that transfers the control flow to that branch and keeps executing it until the cx register becomes 0.

Autonomy of CX

  • Decrement CX by 1.

  • If CX != 0, jump to <Label>.

  • If CX == 0, continue with next instruction.

Example

mov cx, 5        ; Repeat 5 times
start_loop:
    ; some code here

loop start_loop

Note: Every execution of LOOP instruction automatically decreases CX by 1 and checks it. That’s why CX is often called the counter register.


CALL Instruction

To understand the call instruction, let’s take an example first like we did in the Loop instruction above.

Imagine we’re solving a problem: we have 5 sets of numbers. For each set, we want to calculate the sum and the average.

The sum is easy — we can just use the add instruction again and again. Each set has five numbers, and we can go through them one by one using a loop.

For the average, we simply take the sum and divide it by the total count (which is 5). We can use the div instruction for that. Simple enough, right?

Well… yes. But here’s the catch: we don’t just have one set. We have to repeat the exact same process for each of the 5 sets. That’s still manageable — five sets, five sums, five averages.

But then the Lord of Numbers appears and says:

“I have thousands of sets waiting for you, and I’ll keep sending more.”

At that point, Sorry, I’m not a computer science student, I love Picasso. 🎨

To handle situations like we have functions, subroutines, or procedures. What we do in a function is branch all of the required steps one time, and later we just pass the data that we have to perform operations on. They perform operations and give the result back. We can reuse it again and again many time as we want.

We pass data in subroutines with the help of either registers or stacks.

In assembly we have the CALL instruction to call a procedure (function). It saves the current instruction pointer (next instruction) onto the stack and jumps to the given label.

Syntax

call <Label>

Logical Flow

  1. CPU pushes current IP (address of the next instruction) onto the stack.

  2. Jumps to the procedure at <Label>.

  3. Execute all the instruction in the labeled branch until ret instruction come.

  4. When ret instructions comes remove the current IP address from stack and jump back to next instruction after the call instruction.

Example

call my_func     ; Call procedure 'my_func'
...
my_func:
    ; do something
    ret

RET Instruction

In the above example of the call instruction notice we use ret at the end. The RET instruction does nothing but return control back to the location stored on the stack (the instruction after CALL).

Syntax

ret

Logical Flow

  1. CPU pops the return address from the stack.

We haven’t learned stacks in detail, I know! Other then (PUSH or POP)

  1. Loads it into IP (instruction pointer).

  2. Execution resumes where it left off.


Here’s the thing: control flow instructions are what actually transform plain instructions into programs.

Without them, our code would just be a boring list of steps. With them, we can write loops, conditions, functions, and even full applications.

It might feel like a lot of new mnemonics to remember (JMP, JE, JNE, CALL, RET, LOOP, …), but trust me — these are the ones you’ll use all the time when coding in assembly.

System Control Instructions

System control instructions are special instructions that deal with the overall behavior of the CPU.

Unlike data transfer, arithmetic, or control flow instructions that directly manipulate registers or memory, these instructions interact with flags, interrupts, and CPU state.

In simple words, these instructions allow the programmer to:

  • Control how the CPU responds to external events (like hardware interrupts).

  • Modify or preserve the status of the flag register.

  • Halt or wait for events.

They’re not used every day like mov or add, but they are very important for system-level programming (think operating systems, hardware drivers, or critical programs that must handle interrupts).

Let’s explore them one by one:

Flag Control Instructions

Flags are super important because they reflect the result of operations and also control how the CPU executes instructions. Sometimes we want to directly set, clear, or toggle these flags.

Here are some flag-related instructions:

  1. STC (Set Carry Flag)

    • Sets the carry flag (CF) to 1.

    • Useful when we want to force a carry into arithmetic operations.

    Example

    stc       ; CF = 1
  2. CLC (Clear Carry Flag)

    • Clears the carry flag (CF) (sets it to 0).

    Example

    clc       ; CF = 0
  3. CMC (Complement Carry Flag)

    • Toggles (inverts) the carry flag.

    Example

    cmc       ; CF = NOT CF
  4. CLD (Clear Direction Flag)

    • Sets the direction flag (DF) to 0, meaning string instructions (MOVS, CMPS, etc.) will process memory from low to high addresses.

    Example

    cld       ; DF = 0 → process forward
  5. STD (Set Direction Flag)

    • Sets the direction flag (DF) to 1, meaning string instructions process memory from high to low addresses.

    Example

    std       ; DF = 1 → process backward

Interrupt Control Instructions

Interrupts are signals from hardware/software that temporarily stop the CPU to handle something urgent. For example, when you press a key, the keyboard sends an interrupt.

We can enable or disable these interrupts using the following:

  1. STI (Set Interrupt Flag)

    • Enables maskable interrupts (IF = 1).

    • The CPU will now respond to external interrupts.

    Example

    sti       ; Enable interrupts
  2. CLI (Clear Interrupt Flag)

    • Disables maskable interrupts (IF = 0).

    • The CPU will ignore external interrupts until enabled again.

    Example

    cli       ; Disable interrupts

Remember: STI and CLI don’t affect non-maskable interrupts (NMI). Those are hardware-level and cannot be disabled.

  • HLT Instruction (Halt)

    The HLT instruction tells the CPU to stop executing instructions until the next external interrupt occurs.

    It’s often used in operating systems to save power while waiting for something to happen.

    Syntax

    hlt

    Logical Flow

    1. CPU halts execution.

    2. Waits until an interrupt or reset occurs.

    3. Resumes execution after the interrupt handler finishes.

  • WAIT Instruction

    The WAIT instruction pauses the CPU until the Test Pin (external hardware signal) becomes active.

    This one is rarely used in modern programming, but it’s good to know that it exists.

    • ESC Instruction (Escape)

    The ESC instruction is used to hand over control to an external coprocessor (like an FPU — Floating Point Unit).

    In older systems, math-intensive operations were handled by a separate chip, and ESC was the gateway.

    Syntax

    esc <Opcode>, <Operand>
  • LOCK Prefix

    The LOCK prefix isn’t a standalone instruction — it’s used before another instruction to make sure it executes atomically (without interruption).

    This is very important in multi-processor systems, where two CPUs might try to access the same memory at the same time.

    Example

    lock inc [var1]   ; Increment memory location atomically

Woah, that’s a lot of power packed into these little instructions. Unlike mov or add, we won’t use hlt or cli in every program — but when we do, they control the whole CPU!

Take a breather, sip some more water, and give ourselves a credit: we now know the instructions that control the system itself. That’s some boss-level assembly knowledge!

System Control Instruction are kind of very advance topic, that’s why I covered the absolute basics only. However when you feel confident you can explore more.

Last updated

Was this helpful?