Nine bits in a 16-bit register — that’s the entire flag register of the 8086. But those nine bits govern every conditional branch, every signed and unsigned comparison, every string direction, every interrupt decision. Knowing which flag to check after which operation — and especially the four traps that catch almost everyone — is what separates code that works from code that works most of the time.
Flag Register Layout

The 16-bit FLAGS register holds six status flags (CF, PF, AF, ZF, SF, OF) that arithmetic and logic instructions set automatically, and three control flags (TF, IF, DF) that you set deliberately to change CPU behavior. The remaining bits are reserved or always read as 1. Every conditional jump and every REP prefix tests one or more of these flags before deciding whether to act.
Status Flags: What Each One Records
CF — Carry Flag
CF records unsigned overflow. Set when addition carries out of the top bit, or subtraction needs a borrow. Also the bit that shifts out during SHL/SHR/ROL/ROR. The instructions STC, CLC, and CMC set, clear, and toggle CF explicitly. Use JC / JNC after unsigned operations.
; CF set: unsigned carry out
MOV AL, 0FFh
ADD AL, 01h ; AL = 00h, CF = 1 (255 + 1 = 256, overflows 8-bit)
JC overflow_handler
; 32-bit addition using CF chain (ADC = add with carry)
ADD AX, CX ; add low words, CF = carry out
ADC DX, BX ; add high words + CF from previous
OF — Overflow Flag
OF records signed overflow — the result is out of range for two’s complement. The hardware rule: OF = carry into MSB XOR carry out of MSB. In practice: adding two positives and getting a negative, or two negatives and getting a positive. Use JO / JNO or the signed comparison jumps (JL, JG, JLE, JGE) after signed arithmetic.

ZF — Zero Flag
ZF is set when the result is exactly zero. It is the most commonly used flag — CMP sets it when two values are equal (since CMP does subtraction and a zero result means equal), DEC sets it when a counter reaches zero, and TEST sets it when no bits matched a mask.
; CMP + JE/JNE
CMP AX, BX
JE equal ; ZF=1: AX == BX
JNE not_equal ; ZF=0: AX != BX
; DEC loop
MOV CX, 100
loop_top:
; ... body ...
DEC CX
JNZ loop_top ; ZF=0 means CX not yet zero; continue
; TEST: non-destructive bit check
TEST AL, 01h ; AND AL with 01h, discard result, set ZF
JZ bit0_clear ; ZF=1: bit 0 was not set
JNZ bit0_set ; ZF=0: bit 0 was set
SF — Sign Flag
SF equals the MSB (most significant bit) of the result. In two’s complement, MSB=1 means the value is negative. SF alone is enough to detect sign after an arithmetic operation, but never use JS/JNS after CMP for signed comparison — use JL/JG, which correctly combine SF and OF to handle the overflow edge case.
PF — Parity Flag
PF is 1 when the low byte of the result contains an even number of 1-bits (even parity). It reflects only the low byte even for 16-bit operations. Used in serial communication and BCD arithmetic; rarely needed in general code.
AF — Auxiliary Carry Flag
AF records a carry or borrow between bit 3 and bit 4 of the result. Its only purpose is BCD arithmetic: the DAA, DAS, AAA, and AAS instructions inspect AF to decide whether the low nibble needs a ±6 correction to stay within the 0–9 BCD range.
CF and OF are completely independent. CF fires when an operation overflows the unsigned range; OF fires when it overflows the signed range. The same addition can set CF without setting OF, set OF without setting CF, set both, or set neither — depending purely on the bit pattern. Always use JC/JNC after unsigned arithmetic and JL/JG/JO after signed arithmetic. Mixing them silently produces wrong branches.
Control Flags: Three Bits You Set Yourself
DF — Direction Flag
DF controls whether string instructions auto-increment (DF=0, CLD) or auto-decrement (DF=1, STD) SI and DI. Always call CLD before any string instruction unless you explicitly need backward operation. Failing to set DF is one of the most common assembly bugs — if a previous ISR left DF=1, your REP MOVSB will silently copy memory in the wrong direction.
IF — Interrupt Enable Flag
IF=1 allows maskable hardware interrupts (INTR pin). IF=0 blocks them. CLI clears it; STI sets it. NMI and software INT instructions are never affected by IF. Always keep critical sections with CLI as short as possible — they delay every hardware interrupt including the system timer.
TF — Trap Flag
When TF=1, the CPU executes one instruction then automatically generates INT 1 (single-step). TF is cleared by hardware before entering the INT 1 handler, so the handler itself does not single-step. This is used exclusively by debuggers — there is no STF or CTF instruction; set and clear TF via PUSHF/POP AX/modify/PUSH AX/POPF.
Critical Traps: The Four Things That Catch Everyone
These four behaviors surprise every 8086 programmer at least once. None of them produce an error message — they just silently produce wrong results or do nothing when you expect a branch to fire.
1. INC and DEC do not set CF
INC and DEC update SF, ZF, AF, PF, OF — but never CF. This is intentional (so you can increment a pointer inside an ADC chain without disturbing the carry), but it trips up anyone who writes DEC CX; JC somewhere expecting it to detect underflow. Use JZ after DEC to check if the counter reached zero.
2. AND/OR/XOR always clear CF and OF
Logical operations have no concept of carry or overflow. After any AND, OR, XOR, or TEST, CF and OF are always 0 regardless of the operands. If your conditional jump after a TEST doesn’t fire when expected, verify you’re checking ZF (via JZ/JNZ), not CF.
3. Use JL/JG not JS/JNS for signed comparisons
After CMP AX, BX, the result is AX−BX. If this subtraction overflows, SF is wrong. JL (Jump if Less) tests SF≠OF, which correctly handles the overflow case. JS only tests SF alone, which fails when the subtraction result itself overflowed.
4. DIV leaves CF, OF, SF, ZF, PF, AF undefined
After a DIV or IDIV instruction, the values of all six status flags are undefined — the hardware makes no guarantee. Never check any flag after a division.
Conditional Jump Quick Reference
| Mnemonic | Alias | Condition | After CMP A,B |
|---|---|---|---|
| JZ / JE | — | ZF=1 | A == B |
| JNZ / JNE | — | ZF=0 | A != B |
| JC / JB | JNAE | CF=1 | A < B (unsigned) |
| JNC / JNB | JAE | CF=0 | A >= B (unsigned) |
| JA | JNBE | CF=0 AND ZF=0 | A > B (unsigned) |
| JL | JNGE | SF != OF | A < B (signed) |
| JGE | JNL | SF == OF | A >= B (signed) |
| JG | JNLE | ZF=0 AND SF==OF | A > B (signed) |
| JLE | JNG | ZF=1 OR SF!=OF | A <= B (signed) |
Reading and Writing FLAGS Directly
PUSHF ; push FLAGS word onto stack (SP -= 2)
POPF ; pop FLAGS word from stack (SP += 2)
; Inspect or modify FLAGS through AX
PUSHF
POP AX ; AX = FLAGS
OR AX, 0200h ; set bit 9 (IF) = enable interrupts
PUSH AX
POPF
; Single-flag direct instructions
CLC ; CF = 0
STC ; CF = 1
CMC ; CF = NOT CF
CLI ; IF = 0 (disable maskable interrupts)
STI ; IF = 1 (enable maskable interrupts)
CLD ; DF = 0 (string ops go forward: SI++, DI++)
STD ; DF = 1 (string ops go backward: SI--, DI--)
BCD Arithmetic and the AF Flag
Binary-Coded Decimal (BCD) stores each decimal digit in one nibble (4 bits). The 8086 supports BCD arithmetic through six adjustment instructions, all of which depend on or produce the AF flag. The pattern is always the same: perform ordinary binary arithmetic first, then call the adjustment instruction to correct the result back into valid BCD. No other instructions use AF; these six are its entire purpose.
| Instruction | Meaning | Reads | Adjusts | Clocks |
|---|---|---|---|---|
| DAA | Decimal Adjust after Addition | AF, CF | AL: corrects packed BCD addition result | 4 |
| DAS | Decimal Adjust after Subtraction | AF, CF | AL: corrects packed BCD subtraction result | 4 |
| AAA | ASCII Adjust after Addition | AF | AL: unpacked BCD; AH incremented if AF set | 8 |
| AAS | ASCII Adjust after Subtraction | AF | AL: unpacked BCD; AH decremented if AF set | 8 |
| AAM | ASCII Adjust after Multiplication | — | AX: AH = AL ÷ 10, AL = AL mod 10 | 83 |
| AAD | ASCII Adjust before Division | — | AX: AL = AH×10 + AL, AH = 0 | 60 |
; Packed BCD addition: 39 + 48 = 87
MOV AL, 39h ; packed BCD 39 (3 in high nibble, 9 in low)
ADD AL, 48h ; binary result: 81h -- invalid BCD (8 and 1, but carry from low nibble set AF)
DAA ; AL = 87h (AF was set: low nibble 1+8=9 -- no; actually 9+8=17, carry into high nibble)
; DAA: if low nibble > 9 or AF=1, add 06h; if result > 9Fh or CF=1, add 60h
; Unpacked BCD addition from ASCII digits
MOV AL, '5' ; AL = 35h
ADD AL, '3' ; AL = 68h (wrong BCD)
AAA ; AL = 08h (low nibble only), AH incremented if carry, CF/AF set if carry
OR AL, 30h ; convert back to ASCII: AL = '8'
; AAM: convert binary multiply result to unpacked BCD
MOV AL, 7
MOV BL, 9
MUL BL ; AX = 63 (3Fh) -- binary
AAM ; AH = 6, AL = 3 (6 tens, 3 units)
INTO — Interrupt on Overflow
INTO is the overflow-conditional form of INT. If OF=1 when INTO executes, it triggers INT 4 — pushing FLAGS, CS, and IP onto the stack and jumping through the IVT entry at address 00010h. If OF=0, INTO is a 4-clock no-op. It is the hardware companion to JO: where JO branches, INTO dispatches to a dedicated handler. Most real programs leave INT 4’s IVT entry pointing to an IRET (the default), making INTO effectively a conditional NOP unless the program installs its own overflow handler.
; Install an overflow handler, then use INTO
.data
old_of_handler DD 0
.code
; Save original INT 4 vector
MOV AH, 35h
MOV AL, 04h
INT 21h
MOV WORD PTR [old_of_handler], BX
MOV WORD PTR [old_of_handler+2], ES
; Install new handler
MOV AH, 25h
MOV AL, 04h
MOV DX, OFFSET overflow_isr
INT 21h
; Signed addition with overflow check using INTO
MOV AX, 7FFFh ; maximum signed 16-bit value
ADD AX, 0001h ; AX = 8000h = -32768: signed overflow, OF=1
INTO ; OF=1 -> triggers INT 4 -> jumps to overflow_isr
overflow_isr PROC FAR
; handle signed overflow
IRET
overflow_isr ENDP
Read Next & Related Articles
- Read next: ⑦ Stack Operations — PUSHF/POPF for saving and restoring the full flag state across calls
- ⑧ Interrupt System — how INT clears TF and IF; how IRET restores them
- ⑤ Addressing Modes — REPE/REPNE use ZF; string direction uses DF
- ② Register Reference — FLAGS bit-by-bit table within the full register overview
- 8086 Interrupt System — how INT automatically clears TF and IF, how IRET restores them
- 8086 Addressing Modes — how REPE/REPNE use ZF and CF to control string instruction repetition
- 8086 Stack Operations — PUSHF/POPF for saving and restoring the complete flag state
FAQs
Q: Does CMP modify any registers?
No. CMP subtracts the second operand from the first, sets flags based on the result, and discards the result. Neither operand changes.
Q: Why do AND/OR/XOR clear CF and OF to zero?
Logical operations have no mathematical overflow — every possible AND/OR/XOR result is within range by definition. Intel chose CF=OF=0 after logical operations to give predictable, checkable state rather than leaving them undefined.
Q: Is PF useful in modern code?
Rarely. Its historical use was in serial communication drivers that needed to check hardware parity on received bytes. In general assembly programming, PF is almost never examined.