📦
PART 1 — DOSBox Basics

DOSBox is a program that pretends to be an old 1980s computer running DOS (Disk Operating System). We need it because our assembly programs are written for 16-bit x86 DOS — they won't run on a modern OS directly. Think of it as a time machine.

💡 One-line summary: DOSBox = fake old computer that runs your assembly programs.
Essential DOS Commands
CommandWhat it doesExample
mount c c:\assemblyLink your real folder to drive C inside DOSBoxmount c c:\assembly
c:Switch to drive C
dirList all files in the current folder
cd foldernameEnter a foldercd programs
cd ..Go back one folder
md foldernameCreate a new foldermd lesson1
del filenameDelete a filedel hello.asm
type filenamePrint file contents to screentype hello.asm
clsClear the screen
The 4-Step Workflow (Memorize This!)

Every time you write an assembly program, you repeat these exact steps:

DOSBOX TERMINAL
; Step 1 — Write your code in Notepad / VS Code, save as hello.asm
; Step 2 — Assemble (turn .asm into .com executable)
nasm hello.asm -o hello.com

; Step 3 — Run it
hello

; Step 4 — Debug it (optional but very useful)
afd hello.com
AFD Debugger — Your Best Friend

AFD shows your program running one instruction at a time. You can watch registers change live. Use it whenever your program does something unexpected.

KeyAction
F8Step — execute one instruction (use this constantly)
F7Step Into — go inside a function call
F2Set/remove a breakpoint
F9Run until next breakpoint
TabSwitch between the 4 panels
Alt+XExit AFD
🔧
PART 2 — CPU Architecture & Registers

The CPU has tiny ultra-fast storage slots called registers. There's no garbage collector, no safety net — you control everything directly. In C++, variables live in RAM and the compiler manages registers for you. In assembly, you manage every register manually.

General Purpose Registers (the ones you'll use 90% of the time)
AX
16 bits
AH
high 8 bits
AL
low 8 bits

Same layout for BX/BH/BL, CX/CH/CL, DX/DH/DL.

RegisterFull NameMain UseC++ Analogy
AXAccumulatorMath results, return valuesint result;
BXBaseMemory addresses, general useint* ptr;
CXCounterLoop counters (used by LOOP)int i; in a for loop
DXDataI/O, overflow from MUL/DIVint extra;
SI / DISource / Dest IndexPointing into arrays / stringschar* src; char* dst;
SPStack PointerTop of the stack (auto-managed)Managed by compiler
IPInstruction PointerNext instruction to run (you never touch this directly)Program counter
Flags Register

After every arithmetic or logic instruction, the CPU automatically updates these flag bits. Conditional jumps (je, jg, etc.) read these flags.

FlagSet when…Used by
ZF ZeroResult was exactly 0je, jz, jne
SF SignResult's highest bit is 1 (negative in signed math)js, jns
CF CarryAddition carried out of the top bit, or subtraction borrowedjc, jb
OF OverflowSigned result overflowed (too big or too small)jo
Number Systems Cheat
SystemBaseNASM SyntaxExample
Decimal10Just write the number65
Hexadecimal16 (0–9, A–F)Add h suffix or 0x prefix41h = 0x41
Binary2Add b suffix01000001b
💡 ASCII '0' = 48 = 30h. 'A' = 65 = 41h. 'a' = 97 = 61h. That's why we add al, 30h to print a digit — it converts the number to its ASCII character.
📝
PART 3 — NASM Syntax & Program Structure

Every .COM program has the same skeleton. The CPU starts reading from offset 100h in memory, which is why we always write org 100h at the top.

Program Skeleton
ASSEMBLY (NASM)
; org tells NASM: "code starts at 100h"
org 100h

; Jump past data (so CPU doesn't try to
; run your variables as code!)
jmp start

; === DATA ===
myMsg  db 'Hello$'     ; string
myNum  db 42           ; byte
myWord dw 1000         ; 2-byte word

; === CODE ===
start:
    ; your code here

    mov ax, 4C00h  ; exit program
    int 21h
C++ EQUIVALENT
// #include and main() are the equivalent
// of org 100h + jmp start
#include <iostream>
using namespace std;

// === GLOBAL VARIABLES (like db/dw) ===
string myMsg  = "Hello";
char   myNum  = 42;
int    myWord = 1000;

// === CODE (like "start:" label) ===
int main() {
    // your code here

    return 0; // like "mov ax,4C00h / int 21h"
}
Defining Data
Data definitions — db / dw
ASSEMBLY
myByte  db 10         ; 1 byte  (0–255)
myWord  dw 1000       ; 2 bytes (0–65535)
myMsg   db 'Hello$'   ; string ($ = end marker)
myArr   db 1,2,3,4,5 ; array of 5 bytes
C++ EQUIVALENT
char      myByte = 10;
int       myWord = 1000;
string    myMsg  = "Hello";  // null-terminated
char      myArr[] = {1,2,3,4,5};
⚙️
PART 4 — Instructions Reference
4.1 Data Movement — MOV, XCHG, PUSH, POP

MOV dst, src — copies data from src into dst. Think of it as the = assignment operator. The destination is always on the LEFT.

MOV — the assignment operator of assembly
ASSEMBLY
mov ax, 5       ; AX = 5
mov bx, ax      ; BX = AX  (copy register)
mov al, [bx]    ; AL = memory[BX]  (load)
mov [bx], al    ; memory[BX] = AL  (store)

xchg ax, bx     ; swap AX and BX
push ax          ; save AX on the stack
pop  bx          ; restore top of stack → BX
lea  bx, [myVar] ; BX = address of myVar
C++ EQUIVALENT
int ax = 5;
int bx = ax;
ax = *ptr;       // *ptr dereferences a pointer
*ptr = ax;

swap(ax, bx);    // std::swap
stack.push(ax);  // std::stack
bx = stack.top(); stack.pop();
bx = &myVar;     // take the address of myVar
⚠️ MOV rule: You can NEVER mov memory directly to memory. You must go through a register. mov [a], [b] is ILLEGAL.
4.2 Arithmetic — ADD, SUB, MUL, DIV
ADD / SUB / INC / DEC
ASSEMBLY
add ax, 5      ; AX = AX + 5
add ax, bx     ; AX = AX + BX
sub ax, 3      ; AX = AX - 3
inc ax          ; AX = AX + 1
dec ax          ; AX = AX - 1
neg ax          ; AX = -AX
C++ EQUIVALENT
ax += 5;
ax += bx;
ax -= 3;
ax++;  // or ++ax
ax--;  // or --ax
ax = -ax;
MUL / DIV — these work differently!
ASSEMBLY
; 8-bit MUL: AX = AL * operand
mov al, 10
mov bl, 6
mul bl          ; AX = AL * BL = 60
; Result is ALWAYS in AX (both bytes)

; 8-bit DIV: AL = AX / operand
;            AH = remainder
mov ax, 35      ; dividend must be in AX
mov bl, 10
div bl          ; AL = 3 (quotient)
                 ; AH = 5 (remainder)
C++ EQUIVALENT
// MUL
int al = 10, bl = 6;
int ax = al * bl;  // ax = 60




// DIV
int ax = 35;
int bl = 10;
int al = ax / bl;  // al = 3 (quotient)
int ah = ax % bl;  // ah = 5 (remainder)
⚠️ MUL always uses AL as the first operand and puts the result in AX. You can't choose a different register. DIV divides AX and puts quotient in AL, remainder in AH.
4.3 Logic / Bitwise — AND, OR, XOR, NOT

These operate on individual bits. Very useful for checking/setting individual bits and the famous xor ax, ax trick to zero a register.

Bitwise operations
ASSEMBLY
and ax, bx      ; AX = AX AND BX (bit-by-bit)
or  ax, bx      ; AX = AX OR  BX
xor ax, bx      ; AX = AX XOR BX
not ax           ; AX = flip all bits

; ★ TRICK: fastest way to zero a register
xor ax, ax      ; AX = 0  (any value XOR itself = 0)

; Check if a number is even/odd
and al, 01h     ; isolate last bit
; ZF=1 means even, ZF=0 means odd
C++ EQUIVALENT
ax &= bx;   // bitwise AND
ax |= bx;   // bitwise OR
ax ^= bx;   // bitwise XOR
ax = ~ax;   // bitwise NOT

// Zero a variable
ax = 0;     // same result, XOR is just faster in ASM

// Even/odd check
if (al & 1) { /* odd */ }
else        { /* even */ }
4.4 Shifts — SHL / SHR

Shifting left by 1 = multiply by 2. Shifting right by 1 = divide by 2. Much faster than MUL/DIV for powers of 2.

Bit shifts
ASSEMBLY
shl ax, 1   ; AX = AX * 2  (shift bits left)
shl ax, 3   ; AX = AX * 8  (left by 3)
shr ax, 1   ; AX = AX / 2  (unsigned)
sar ax, 1   ; AX = AX / 2  (signed, keeps sign bit)
C++ EQUIVALENT
ax <<= 1;  // ax *= 2
ax <<= 3;  // ax *= 8
ax >>= 1;  // ax /= 2  (unsigned)
ax >>= 1;  // ax /= 2  (signed, same in C++)
4.5 Compare & Jump — the If-Statement of Assembly

CMP a, b subtracts b from a without saving the result — it just sets the CPU flags. Then a conditional jump reads those flags.

CMP + conditional jump = if statement
ASSEMBLY
cmp ax, bx        ; compare AX and BX
je  equal_branch  ; jump if AX == BX
jne neq_branch    ; jump if AX != BX
jg  bigger        ; jump if AX >  BX (signed)
jl  smaller       ; jump if AX <  BX (signed)
jge atleast       ; jump if AX >= BX (signed)
jle atmost        ; jump if AX <= BX (signed)
jmp always        ; unconditional jump (goto)
C++ EQUIVALENT
// cmp sets the "mood", jump acts on it
if (ax == bx)  goto equal_branch;
if (ax != bx)  goto neq_branch;
if (ax  > bx)  goto bigger;
if (ax  < bx)  goto smaller;
if (ax >= bx)  goto atleast;
if (ax <= bx)  goto atmost;
goto always;
🔎 In C++ you use goto almost never. In assembly, every conditional is a jump — there's no other choice. Don't be scared of labels; they're just named memory addresses.
4.6 Procedures (Functions)
CALL / RET — function call and return
ASSEMBLY
; Calling a procedure
call myFunc      ; push IP, jump to myFunc

; ... later in your code ...
myFunc:
    ; body of the function
    mov ax, 42   ; do something
    ret           ; pop IP, jump back to caller
C++ EQUIVALENT
// Calling a function
myFunc();

// ... the function definition ...
void myFunc() {
    // body
    ax = 42;
    return;  // ret
}
4.7 INT 21h — Your I/O Toolkit

INT 21h is a DOS system call. You set AH to a function number, set up any inputs, then call int 21h. DOS does the work.

Common INT 21h functions
ASSEMBLY
; Print one character (DL = char)
mov ah, 02h
mov dl, 'A'
int 21h

; Print a string (ends with '$')
mov ah, 09h
lea dx, [myStr]
int 21h

; Read one character → AL
mov ah, 01h
int 21h          ; AL = key pressed

; Exit program
mov ax, 4C00h
int 21h
C++ EQUIVALENT
// Print one character
cout << 'A';



// Print a string
cout << myStr;



// Read one character
char ch = cin.get();  // or getchar()


// Exit program
return 0;
💻
PART 5 — Printing Characters & Strings
Hello, World!
hello.asm — Print a string using INT 21h function 09h
ASSEMBLY
org 100h

jmp start

; String MUST end with '$' — that is how
; DOS knows where the string ends
message db 'Hello, World!', 13, 10, '$'
;                              ^    ^    ^
;                             CR   LF  terminator

start:
    mov ah, 09h        ; function 09h = print string
    lea dx, [message]  ; DX = address of string
    int 21h            ; DOS prints until '$'

    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
#include <iostream>
using namespace std;

int main() {

    // endl adds '\n' (like 13,10 in ASM)
    cout << "Hello, World!" << endl;

    return 0;
}
Why jmp start? The CPU reads memory top to bottom. If we don't jump over the data, the CPU tries to execute 'H','e','l','l','o' as machine instructions and crashes. The jmp start skips over the data safely.
PART 6 — Arithmetic & Printing Numbers

Numbers in registers are raw binary. To print a number, you must convert each digit to its ASCII character by adding 30h (48 in decimal). That's because ASCII '0' = 48 = 30h.

print_result.asm — Compute 3+4 and print the digit '7'
ASSEMBLY
org 100h

start:
    mov al, 3        ; AL = 3
    add al, 4        ; AL = 7

    ; Convert digit 7 → ASCII '7'
    add al, 30h      ; AL = 7 + 48 = 55 = '7'

    mov ah, 02h      ; DOS print-char function
    mov dl, al       ; DL = '7'
    int 21h          ; prints '7'

    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
#include <iostream>
using namespace std;
int main() {
    int al = 3 + 4;  // al = 7

    // C++ prints integers directly
    // No ASCII conversion needed!
    cout << al;       // prints 7

    return 0;
}
twodigit.asm — Print a two-digit number (e.g., 35)
ASSEMBLY
org 100h

start:
    mov ax, 35      ; number to print

    ; Divide 35 by 10:
    ; AL = 3 (tens digit), AH = 5 (units digit)
    mov bl, 10
    div bl

    mov cl, ah     ; save units (AH gets clobbered)

    ; Print tens digit (AL=3)
    add al, 30h    ; '3'
    mov ah, 02h
    mov dl, al
    int 21h        ; prints '3'

    ; Print units digit (CL=5)
    add cl, 30h    ; '5'
    mov ah, 02h
    mov dl, cl
    int 21h        ; prints '5'

    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
#include <iostream>
using namespace std;
int main() {
    int num = 35;

    // C++ does this automatically with cout
    cout << num;  // prints "35"

    // But in ASM we must split it:
    int tens  = num / 10; // 3
    int units = num % 10; // 5
    cout << (char)(tens  + '0');
    cout << (char)(units + '0');
    // +48 is same as +'0' (adding 30h)

    return 0;
}
🔀
PART 7 — Conditions (If / Else)

Assembly doesn't have if or else keywords. You simulate them with cmp (which sets flags) and conditional jumps that skip over blocks of code.

is_zero.asm — Check if a number equals zero
ASSEMBLY
org 100h
jmp start

number  db 0   ; change this to test
msg_yes db 'The number is zero!', 13, 10, '$'
msg_no  db 'Not zero!', 13, 10, '$'

start:
    mov al, [number]  ; load number into AL
    cmp al, 0         ; AL - 0 (sets ZF if result=0)
    je  is_zero       ; jump if ZF=1 (equal)

    ; === NOT ZERO branch ===
    mov ah, 09h
    lea dx, [msg_no]
    int 21h
    jmp done          ; MUST skip the zero branch

is_zero:
    ; === ZERO branch ===
    mov ah, 09h
    lea dx, [msg_yes]
    int 21h

done:
    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
#include <iostream>
using namespace std;
int main() {
    int number = 0;

    if (number == 0) {
        cout << "The number is zero!\n";
    } else {
        cout << "Not zero!\n";
    }

    return 0;
}
grade.asm — Multi-branch: if / else-if / else (Grade checker)
ASSEMBLY
start:
    mov al, [marks]

    ; Check from highest down (ORDER MATTERS!)
    cmp al, 90
    jge get_A     ; if marks >= 90 → A

    cmp al, 75
    jge get_B     ; else if marks >= 75 → B

    cmp al, 60
    jge get_C     ; else if marks >= 60 → C

    cmp al, 50
    jge get_D     ; else if marks >= 50 → D

    ; else → F (fall through)
    lea dx, [grade_f] / jmp done
get_A: ... get_B: ... get_C: ... get_D: ...
C++ EQUIVALENT
int marks = 75;

if      (marks >= 90) cout << "Grade A";
else if (marks >= 75) cout << "Grade B";
else if (marks >= 60) cout << "Grade C";
else if (marks >= 50) cout << "Grade D";
else                   cout << "Grade F";
⚠️ Always jmp over other branches! After an if-branch runs, you must jmp done to skip the else-branches. Forgetting this is the #1 beginner bug — you'll fall through into the next branch.
🔁
PART 8 — Loops

Assembly has two main loop patterns: the LOOP instruction (which uses CX as a counter), and the CMP + JMP pattern (which is a while-loop).

stars.asm — LOOP instruction (for-loop with fixed count)
ASSEMBLY
org 100h

start:
    mov cx, 5         ; CX = loop count (5 iterations)

print_loop:
    ; --- loop body ---
    mov ah, 02h
    mov dl, '*'
    int 21h           ; print '*'

    loop print_loop   ; CX-- ; if CX != 0, jump back
    ; when CX hits 0, falls through here

    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
#include <iostream>
using namespace std;
int main() {
    // LOOP = for loop with CX as counter
    for (int cx = 5; cx > 0; cx--) {
        cout << '*';
    }
    // Output: *****
    return 0;
}
count9.asm — Loop that prints 1 through 9
ASSEMBLY
start:
    mov cx, 9         ; 9 iterations
    mov bl, 1         ; BL = current digit

number_loop:
    mov al, bl        ; AL = current digit
    add al, 30h       ; convert to ASCII
    mov ah, 02h
    mov dl, al
    int 21h           ; print digit

    inc bl            ; BL++ (next digit)
    loop number_loop  ; CX--; repeat

    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
// Notice the assembly uses TWO variables:
// CX = how many left, BL = current value

for (int bl = 1; bl <= 9; bl++) {
    cout << bl << ' ';
}
// Output: 1 2 3 4 5 6 7 8 9

// In ASM: cout is replaced by
// the add+30h+int21h sequence
whileloop.asm — CMP+JMP pattern (while-loop)
ASSEMBLY
; This is the while-loop pattern:
; Check condition → execute body → jump back

start:
    mov al, 1         ; al = starting value

while_start:
    cmp al, 10        ; is al >= 10?
    jge while_end    ; yes → exit loop

    ; === loop body ===
    ; (do something with al)
    inc al            ; al++
    jmp while_start  ; go back to check

while_end:
    mov ax, 4C00h
    int 21h
C++ EQUIVALENT
int al = 1;

while (al < 10) {
    // loop body
    al++;
}

// The CMP+JGE+JMP is EXACTLY
// the while condition check:
//   jge = "if NOT (al < 10) then exit"
//   jmp = "go back to check again"
⚠️ LOOP uses CX — protect it! If your loop body uses CALL or anything that changes CX (like INT 21h sometimes can), you must save CX with push cx before and pop cx after. See the Stack section.
📥
PART 9 — Reading User Input
key_check.asm — Read a key and respond
ASSEMBLY
org 100h
jmp start

prompt    db 'Press Y or N: $'
msg_yes   db 13, 10, 'You said YES!$'
msg_no    db 13, 10, 'You said NO!$'

start:
    mov ah, 09h       ; print prompt
    lea dx, [prompt]
    int 21h

    mov ah, 01h       ; READ one char → AL
    int 21h           ; AL = ASCII of pressed key

    cmp al, 'Y'       ; was it 'Y'?
    je  pressed_yes
    cmp al, 'y'       ; or lowercase 'y'?
    je  pressed_yes

    ; else: not Y
    lea dx, [msg_no]  / jmp done

pressed_yes:
    lea dx, [msg_yes]

done:
    mov ah, 09h / int 21h
    mov ax, 4C00h / int 21h
C++ EQUIVALENT
#include <iostream>
using namespace std;
int main() {
    cout << "Press Y or N: ";

    char ch;
    ch = getchar();  // like "mov ah,01h / int 21h"
    // ch now holds the character (like AL)

    if (ch == 'Y' || ch == 'y') {
        cout << "\nYou said YES!";
    } else {
        cout << "\nYou said NO!";
    }

    return 0;
}
📌 After int 21h with AH=01h, the character's ASCII code is in AL. You then use cmp al, 'Y' to check what key was pressed. In C++, the char variable holds the character directly — no conversion needed.
🧩
PART 10 — Procedures (Functions)
procedure.asm — Define and call a reusable print procedure
ASSEMBLY
org 100h
jmp start

msg1 db 'Hello$'
msg2 db 'World$'

start:
    ; Call print_str twice with different strings
    lea dx, [msg1]   ; "pass argument" in DX
    call print_str

    lea dx, [msg2]
    call print_str

    mov ax, 4C00h
    int 21h

; ---- PROCEDURE DEFINITION ----
; Input: DX = address of '$'-terminated string
print_str:
    mov ah, 09h
    int 21h     ; prints the string at [DX]
    ret          ; return to caller
C++ EQUIVALENT
#include <iostream>
using namespace std;

// Parameters replace the DX register
void print_str(string msg) {
    cout << msg;
    // ret → just return from function
}

int main() {
    print_str("Hello");
    print_str("World");
    return 0;
}
📌 In assembly, you pass arguments by putting values in specific registers before calling. This is a convention (called a "calling convention") — you must document it in comments. C++ does this automatically with function parameters.
📚
PART 11 — The Stack

The stack is like a plate-stacking machine: LIFO — Last In, First Out. PUSH adds a plate on top. POP removes the top plate. You use the stack to save registers before a procedure messes with them, then restore them after.

PUSH / POP — save and restore registers
ASSEMBLY
; Save AX and BX before calling something
; that might change them
push ax       ; save AX (goes on top of stack)
push bx       ; save BX (goes on top of AX)

call someProc ; this might change AX and BX

; Restore in REVERSE order!
pop  bx       ; restore BX first (LIFO!)
pop  ax       ; restore AX second
; AX and BX are back to what they were
C++ EQUIVALENT
// C++ does this automatically!
// When you call a function, the compiler
// generates push/pop instructions for you.

int ax = 5, bx = 10;

someFunc();  // compiler saves ax,bx on stack,
             // calls the function, restores them

// ax and bx are unchanged after the call
// In ASM you must do this manually!
Golden Rule: Every PUSH must have exactly one matching POP. If you PUSH 3 times and POP only 2 times, your program will crash because the return address on the stack gets corrupted.
🗃️
PART 12 — Arrays

Arrays in assembly are just bytes stored one after another in memory. You access elements using a base address + offset. There's no bounds checking — if you go past the end, you corrupt memory.

array.asm — Access elements with BX as index
ASSEMBLY
org 100h
jmp start

; Define an array of 5 bytes
myArr db 10, 20, 30, 40, 50

start:
    ; Access myArr[0] → first element
    mov al, [myArr]     ; AL = 10

    ; Access myArr[2] → third element
    ; Use BX as the index (offset from base)
    lea bx, [myArr]    ; BX = start address
    mov al, [bx+2]     ; AL = myArr[2] = 30

    ; Loop through all 5 elements
    mov cx, 5           ; 5 elements
    mov si, 0           ; SI = index
loop_arr:
    mov al, [myArr+si] ; AL = myArr[si]
    inc si              ; si++
    loop loop_arr
C++ EQUIVALENT
char myArr[] = {10, 20, 30, 40, 50};

// Access element 0
char al = myArr[0];  // al = 10

// Access element 2
al = myArr[2];       // al = 30
// [bx+2] in ASM = myArr[2] in C++

// Loop through all elements
for (int i = 0; i < 5; i++) {
    al = myArr[i];   // [myArr+si] in ASM
}
📋
PART 14 — Quick Reference
All Instructions at a Glance
InstructionWhat it doesC++ equivalent
mov dst, srcdst = srcdst = src;
add dst, srcdst = dst + srcdst += src;
sub dst, srcdst = dst - srcdst -= src;
inc dstdst = dst + 1dst++;
dec dstdst = dst - 1dst--;
mul srcAX = AL * src (8-bit)ax = al * src;
div srcAL = AX / src, AH = AX % srcal = ax/src; ah = ax%src;
neg dstdst = -dstdst = -dst;
and dst, srcdst = dst & src (bitwise)dst &= src;
or dst, srcdst = dst | srcdst |= src;
xor dst, srcdst = dst ^ srcdst ^= src;
xor ax, axax = 0 (fastest zero)ax = 0;
not dstdst = ~dstdst = ~dst;
shl dst, ndst = dst << n (×2ⁿ)dst <<= n;
shr dst, ndst = dst >> n (÷2ⁿ, unsigned)dst >>= n;
cmp a, bcompute a-b, set flags (don't save)used inside if/while
je / jzjump if equal / zeroif (a == b)
jne / jnzjump if not equalif (a != b)
jgjump if greater (signed)if (a > b)
jljump if less (signed)if (a < b)
jgejump if ≥ (signed)if (a >= b)
jlejump if ≤ (signed)if (a <= b)
jmp lblunconditional jumpgoto lbl;
loop lblCX--; if CX≠0 jumpfor(cx;cx>0;cx--)
call procpush IP, jump to procproc();
retpop IP, returnreturn;
push srcpush onto stackcompiler does this
pop dstpop from stackcompiler does this
Common Patterns (Copy these templates!)
Essential patterns you'll use in every program
ASSEMBLY PATTERN
; ── Print a newline ──
mov ah, 02h / mov dl, 13 / int 21h  ; CR
mov ah, 02h / mov dl, 10 / int 21h  ; LF

; ── Number → ASCII char ──
add al, 30h  ; digit 7 → character '7'

; ── ASCII char → Number ──
sub al, 30h  ; character '7' → digit 7

; ── Zero a register (fastest) ──
xor ax, ax

; ── Count-controlled loop ──
mov cx, N
myloop:
    ; body
    loop myloop

; ── If-else ──
    cmp al, val
    je  true_branch
    ; else code
    jmp endif
true_branch:
    ; if code
endif:

; ── Save/restore registers ──
push ax / push bx   ; save
    ; ... code ...
pop  bx / pop  ax   ; restore (REVERSE!)
C++ EQUIVALENT
// ── Print a newline ──
cout << endl;
// or: cout << '\n';


// ── Number → char ──
char c = al + '0';  // '0' == 48 == 30h

// ── char → Number ──
int n = c - '0';

// ── Zero ──
ax = 0;

// ── Count-controlled loop ──
for (int i = 0; i < N; i++) {
    // body
}

// ── If-else ──
if (al == val) {
    // true code
} else {
    // else code
}

// ── Save/restore ──
// The compiler does this for you
// automatically in every function call
Complete Workflow
TERMINALThe 6 steps, every time
STEP 1  Write code in Notepad/VS Code → save as myprogram.asm in C:\assembly

STEP 2  In DOSBox:
         mount c c:\assembly
         c:

STEP 3  Assemble:
         nasm myprogram.asm -o myprogram.com
         (if no error message appears, success!)

STEP 4  Run:
         myprogram

STEP 5  Debug (if something is wrong):
         afd myprogram.com
         F8 = step one instruction at a time
         Tab = switch between panels (Code / Registers / Flags / Memory)

STEP 6  Edit → Re-save → Reassemble → Rerun → Repeat
💬 Final words for beginners:
Assembly punishes laziness and rewards patience. There are no safety nets, no helpful error messages.
1. Type every program yourself. Never copy-paste.
2. Run it through AFD. Watch every register change with F8.
3. Change one thing. See what breaks. Fix it.
4. Comment every line until you understand why it is there.

Struggle is normal. Confusion is normal. When your program finally works, you will have earned it.