8086 Flag Register: All 9 Flags, Conditional Jumps, and Critical Traps

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

8086 FLAGS register bit layout showing all 9 active bits
The 8086 FLAGS register. 9 active bits; bits 1 and 5 always read as 1; bits 12–15 are unused.

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.

CF and OF independence diagram showing same addition can set one, both, or neither
CF and OF are independent. The same addition can set one, both, or neither.

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.

⚡ Key Takeaway — CF vs OF: Unsigned vs Signed

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

⚠ Watch Out — The Four Traps

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

MnemonicAliasConditionAfter CMP A,B
JZ / JE—ZF=1A == B
JNZ / JNE—ZF=0A != B
JC / JBJNAECF=1A < B (unsigned)
JNC / JNBJAECF=0A >= B (unsigned)
JAJNBECF=0 AND ZF=0A > B (unsigned)
JLJNGESF != OFA < B (signed)
JGEJNLSF == OFA >= B (signed)
JGJNLEZF=0 AND SF==OFA > B (signed)
JLEJNGZF=1 OR SF!=OFA <= 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
DAADecimal Adjust after AdditionAF, CFAL: corrects packed BCD addition result4
DASDecimal Adjust after SubtractionAF, CFAL: corrects packed BCD subtraction result4
AAAASCII Adjust after AdditionAFAL: unpacked BCD; AH incremented if AF set8
AASASCII Adjust after SubtractionAFAL: unpacked BCD; AH decremented if AF set8
AAMASCII Adjust after MultiplicationAX: AH = AL ÷ 10, AL = AL mod 1083
AADASCII Adjust before DivisionAX: AL = AH×10 + AL, AH = 060
; 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

📚 Recommended Reading Order

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.