Skip to main content

Top 60 8086 Viva Questions and Answers (2026)

60 8086 microprocessor viva questions and answers for Sunday-evening exam prep, covering architecture, addressing modes, arithmetic and flags, the stack, interrupts and DOS/BIOS calls, each answer linked to a worked program or reference post.

A viva on the 8086 rewards a specific kind of answer: not the definition off a slide, but the trace — what a particular instruction actually does to a particular register, flag or byte of memory, right now, on this operand. The questions below are organised the way an oral exam usually moves, from the chip’s own architecture through addressing, arithmetic, control flow and interrupts, and into the data directives and DOS/BIOS calls that turn a program into something that runs. This page has 60 questions for Sunday-evening exam prep. Every answer links to one of the worked assembly programs or reference posts on this blog where the behaviour is shown running, not just described, so an answer you are unsure of has somewhere to go for the full program rather than stopping at the one-paragraph version here.
Versions and how this was checked. All numbers on this page were produced by assembling with NASM restricted to the 8086 instruction set (cpu 8086) and running the result in a 16-bit real-mode Unicorn CPU emulator, then reading registers, flags and memory back from the emulator rather than working them out by hand or quoting them from memory. That combination enforces the real 8086’s instruction set — it rejects 80186-and-later forms such as PUSH imm or three-operand IMUL the same way a strict 8086 assembler would — but it is not a real 8086: it runs on a modern x86 core in real mode, so it was used for arithmetic, flags, addressing and control flow only, never for bus timing, pin-level behaviour, or hardware quirks specific to the physical chip.

This page has no companion repository and nothing on it was re-run against the original DOS sessions behind the linked programs. Retrieved 22 September 2026.
Use the table to jump to the topic your viva is about. Each answer ends with a link to the full program or reference post it is drawn from.
If the viva is about…Start at question
BIU/EU, registers, physical addresses, the reset vector1 to 11
Addressing modes, effective addresses, string instructions12 to 19
ADD/SUB/MUL/DIV, BCD adjustment, the flag register20 to 32
Conditional jumps, LOOP, the stack, CALL/RET, recursion33 to 43
INT n, the interrupt vector table, hardware interrupts, IRET44 to 49
Data directives, DOS/BIOS calls, what the assembler rejects50 to 60

Architecture, registers and memory segmentation

Viva examiners open here because it separates a student who has run programs from one who understands why the machine is organised the way it is. These eleven come up in almost every oral exam on the 8086.

1. What are the BIU and the EU, and why does the 8086 split its CPU into two units?

The Bus Interface Unit (BIU) talks to memory and I/O: it calculates 20-bit physical addresses, fetches instruction bytes into a 6-byte prefetch queue, and reads or writes operands. The Execution Unit (EU) never touches the address or data bus itself; it decodes and executes whatever the BIU has queued up, using the ALU and the general-purpose registers. Splitting the two lets them overlap: while the EU is busy executing one instruction, the BIU is already fetching the bytes of the next one into the queue, instead of the two units taking turns on the bus. That overlap is the 8086’s rudimentary pipeline, and it is the reason a jump or a call is comparatively expensive — it invalidates the queue and the BIU has to refill it from the new address before the EU has anything to execute. Explained in: 8086 Microprocessor Architecture: BIU, EU, Pipeline, Pin Diagram, and I/O.

2. What are the 8086’s general-purpose registers, and what is each one actually for?

AX, BX, CX and DX are 16-bit, and each is really two independently addressable 8-bit halves: AH/AL, BH/BL, CH/CL, DH/DL. They are general-purpose in the sense that you can move data through any of them, but the instruction set gives each one a job it is implicitly used for. AX is the accumulator: MUL, DIV, IN and OUT all read or write it without you naming it. BX is the base register and the only one of the four that can appear inside [ ] to address memory. CX is the counter: LOOP and the REP prefixes decrement it automatically, and shift and rotate instructions take their count from CL. DX carries the high half of a 32-bit product or dividend for MUL/DIV, and holds the port number for IN/OUT when the port number does not fit in one byte. SI, DI, BP and SP are the index and pointer registers: SI and DI walk through arrays and strings, BP addresses stack-frame parameters, and SP always points at the current top of the stack. Explained in: The Complete 8086 Register Reference: AX, BX, CX, DX, Segment, Index & Pointer Registers Explained.

3. Why can AX be split into AH and AL, but SI cannot be split the same way?

Only the four data registers, AX, BX, CX and DX, were given independently addressable byte halves, so an instruction can target AH or AL without disturbing the other half. SI, DI, BP and SP have no such split; they are 16-bit only. This is a deliberate asymmetry, not an oversight: the four registers with byte access are the ones the 8-bit forms of arithmetic and I/O instructions need, while the index and pointer registers exist to hold 16-bit offsets, where a byte half would not mean anything. Explained in: The Complete 8086 Register Reference: AX, BX, CX, DX, Segment, Index & Pointer Registers Explained.

4. What are the segment registers, and why does the 8086 need four of them?

CS, DS, SS and ES each hold the upper 16 bits of a base address for one purpose: CS for the code currently executing, DS for the default data segment, SS for the stack, and ES as an extra data segment, most often the destination for string instructions. Every memory reference the CPU makes combines one of these four with a 16-bit offset, and the choice of which segment register applies to which kind of reference is fixed by the addressing mode, not left to you — though a segment-override prefix (ES:, CS:, SS:, DS:) can force a different one for a single instruction. Explained in: Introduction to Memory Segmentation in 8086.

5. How is a 20-bit physical address formed from a segment:offset pair?

The BIU shifts the 16-bit segment value left by 4 bits (equivalently, multiplies it by 16) and adds the 16-bit offset:
physical address = (segment << 4) + offset
For 2000h:0150h that is (0x2000 << 4) + 0x150 = 0x20000 + 0x150 = 0x20150h. The result is a 20-bit value, giving the 8086 a 1 MB address space (2⁵⁰ = 1,048,576 bytes) even though every general-purpose register is only 16 bits wide.
One physical address, many segment:offset names for it Segment 2000h Offset 0150h << 4 bits 20000h Physical address 20150h + 0020h : 0010h 0000h : 0210h same physical byte 00210h The shift-and-add on top turns one segment:offset pair into one physical address. Below it, two unrelated-looking pairs collapse onto the same byte — segmentation lets many addresses alias one another.
The bottom half of the figure is the aliasing fact the next question asks about: raise the segment by 1 and lower the offset by 16, and the shift-and-add lands on the same physical byte. Explained in: Introduction to Memory Segmentation in 8086.

6. Why can two completely different segment:offset pairs point at the exact same byte of memory?

Because the formula overlaps: raising the segment by 1 and lowering the offset by 16 lands on the same physical address, so 0020h:0010h and 0000h:0210h both resolve to physical address 00210h. This is called aliasing, and it is normal rather than a bug — for every physical address there are up to 4,096 segment:offset pairs that name it, which is why two programs, or two labels in the same program, can appear to disagree about an address and both be right. It is also why comparing two far pointers for equality by comparing their segment and offset separately is wrong; you have to normalise both to physical addresses first. Explained in: Introduction to Memory Segmentation in 8086.

7. What is the 8086’s total addressable memory, and where does the number come from?

1 MB, exactly 1,048,576 bytes, because the physical address computed from segment:offset is a 20-bit value and 2⁵⁰ = 1,048,576. This has nothing to do with any register being 20 bits wide — no 8086 register is — it is purely a property of the external address bus and the adder that combines segment and offset before the bytes reach it. Explained in: Introduction to Memory Segmentation in 8086.

8. What happens at address FFFF0h, and why does it matter?

FFFF0h is the 8086’s reset vector: on power-up or reset, CS is loaded with FFFFh and IP with 0000h, so the very first instruction fetched comes from physical address (0xFFFF << 4) + 0x0000 = 0xFFFF0. That address sits 16 bytes below the very top of the 1 MB address space, which is why the reset instruction is almost always a far jump — there is only room for a handful of bytes before the address space runs out, nowhere near enough for a real bootstrap routine. Explained in: 8086 Microprocessor Architecture: BIU, EU, Pipeline, Pin Diagram, and I/O.

9. What is minimum mode versus maximum mode on an 8086?

The 8086 is a 40-pin DIP chip, and several of those pins change meaning depending on one input, MN/MX̄. Tied high, the chip runs in minimum mode: a single-processor system where the 8086 itself generates the memory and I/O control signals directly. Tied low, it runs in maximum mode, intended for multiprocessor systems, where those control signals are instead encoded as status lines and decoded by a separate bus controller chip (the 8288), freeing pins for coprocessor coordination signals. A viva answer only needs the distinction: minimum mode drives its own bus controls, maximum mode delegates them. Explained in: 8086 Microprocessor Architecture: BIU, EU, Pipeline, Pin Diagram, and I/O.

10. What does IP do, and how is it different from the “program counter” you may have read about in other architectures?

IP (the instruction pointer) holds the offset, relative to CS, of the next instruction to fetch; it plays the same conceptual role as a program counter, but it is never used on its own. Every instruction fetch combines CS and IP through the same segment:offset formula as any other memory access, so the address of the next instruction is (CS << 4) + IP, not IP by itself. IP is also not directly readable or writable by a general-purpose MOV; it changes only as a side effect of execution, of a jump, call or return, or of an interrupt. Explained in: The Complete 8086 Register Reference: AX, BX, CX, DX, Segment, Index & Pointer Registers Explained.

11. What is the prefetch queue, and what happens to it on a jump?

The BIU keeps a 6-byte instruction queue, filling it from memory whenever the bus is free and the queue is not already full, so the EU usually finds the next instruction’s bytes already waiting rather than having to wait on a fetch. A jump, call, return or taken conditional branch changes IP to somewhere the queue was not prefetching from, so every byte currently sitting in the queue is discarded and the BIU has to fill it again from the new address before execution can continue. That refill is real, visible time, which is the underlying reason branches cost more than straight-line code on the 8086. Explained in: 8086 Microprocessor Architecture: BIU, EU, Pipeline, Pin Diagram, and I/O.

Addressing modes, effective addresses and string instructions

These are the questions that catch students who have memorised programs without noticing which registers were allowed to appear inside the brackets, and why.

12. What addressing modes does the 8086 support for a memory operand?

Register addressing (the operand is a register, no memory involved), immediate addressing (a constant embedded in the instruction), direct addressing (a fixed address written in the source, MOV AX, [0x300]), register-indirect ([BX], [SI], [DI], [BP]), based ([BX+disp] or [BP+disp]), indexed ([SI+disp] or [DI+disp]), and based-indexed, which combines one base with one index and an optional displacement ([BX+SI], [BX+DI+4], and so on). The effective address is whatever that expression evaluates to; the BIU then adds the segment base to turn it into a physical address. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

13. Why is MOV AX, [CX] rejected by an assembler, when MOV AX, [BX] is accepted?

Only four registers are wired into the 8086’s effective-address hardware as memory pointers: BX and BP as bases, SI and DI as indexes. AX, CX, DX and SP have no corresponding addressing-mode encoding, so [AX], [CX], [DX] and [SP] are not legal 16-bit memory operands at all — an assembler targeting the real instruction set rejects them at assemble time rather than producing a working but unusual encoding. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

14. Why are [SI+DI] and [BX+BP] invalid, when [BX+SI] is fine?

The based-indexed encoding needs exactly one base register (BX or BP) and exactly one index register (SI or DI); the hardware only has an encoding for combining one of each. [SI+DI] pairs two index registers with no base, and [BX+BP] pairs two bases with no index, so neither matches any of the eight legal base+index combinations and both are rejected at assemble time, the same way [CX] is. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

15. What is the difference between LEA and a plain MOV when you want an address in a register?

LEA AX, [BX+SI+4] computes the effective address the brackets describe and puts that number in AX — it never reads memory. MOV AX, [BX+SI+4] computes the exact same effective address but then dereferences it, loading whatever word is stored there. They can look almost identical on the page and do completely different things; the bracket is doing double duty as either “the address” or “the value at the address” depending on which instruction wraps it. Explained in: The Complete 8086 Register Reference: AX, BX, CX, DX, Segment, Index & Pointer Registers Explained.

16. Why does [BP] read from the stack segment while [BX] reads from the data segment, with no override written anywhere?

The default segment for a memory operand depends on which register forms the base, not on anything the programmer writes. BX, SI and DI default to DS; BP (and SP, for stack operations) defaults to SS. That default exists because BP’s whole purpose is addressing stack-frame data, which lives in the segment SS points at, so making it default there saves a segment-override byte on every single stack-relative access. [BX] and [BP] can address completely different bytes even holding the identical offset, because they are relative to two different segments unless you override one of them. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

17. What do MOVS, LODS, STOS, CMPS and SCAS do, and which segment register does each side use?

They are the string instructions, each working on one byte (B suffix) or one word (W suffix) at a time, addressed through SI and DI rather than through an operand you write out. MOVSB/MOVSW copies [SI] to [DI]; LODSB/LODSW loads [SI] into AL/AX; STOSB/STOSW stores AL/AX into [DI]; CMPSB/CMPSW compares [SI] with [DI] and sets flags like CMP; SCASB/SCASW compares AL/AX against [DI]. The source side (SI) is DS-relative and can be overridden; the destination side (DI) is always ES-relative and cannot be overridden — which is why every string-copy routine sets up ES even when DS and ES point at the same place. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

18. What does the direction flag control, and what is the difference between CLD and STD?

DF decides which way SI and DI move after each string operation: CLD clears DF, so SI and DI increment (the string is processed low address to high address); STD sets DF, so they decrement (high to low). Forgetting to set the direction you actually need is a classic silent bug — the instruction executes without complaint and produces a mirror-image or overlapping result instead of an error. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

19. What is the difference between the plain REP prefix and REPE/REPNE?

All three tie a string instruction to CX, decrementing it once per pass and stopping the instruction once CX reaches zero — the same mechanism LOOP uses, just automatic. Plain REP is unconditional and belongs on MOVS and STOS, which have no comparison to make. REPE/REPZ also stops early the moment ZF becomes 0 (a mismatch), and REPNE/REPNZ stops early the moment ZF becomes 1 (a match); those belong on CMPS and SCAS, where you are searching for the first place two things differ or the first place a value is found. Reading CX and the flags after the instruction tells you which of the two stop conditions actually fired. Explained in: 8086 Addressing Modes: EA Calculation, String Instructions, and REP Prefixes.

Arithmetic, BCD adjustment and the flag register

This is the longest section because it is where most marks are actually lost: not on whether a student can write ADD, but on whether they can say what happened to the flags afterward, and why.

20. How do you add two 16-bit numbers, and what does the carry flag tell you afterward?

A plain ADD AX, BX adds the two 16-bit values and leaves the sum in AX. 0202h + 0408h = 060Ah with no carry out. CF is set only when the true sum needed a 17th bit that did not fit — add 0xFFFF and 0x0001 and AX comes back 0000h with CF set, which is the signal that the real answer was 10000h, one bit wider than the register. Explained in: 8086 Assembly Program to Add Two 16-bit Numbers.

21. How do you add two 32-bit numbers using only 16-bit registers?

Add the low 16-bit halves with a plain ADD, which may set CF, then add the high halves with ADC (add with carry) instead of ADD, so any carry out of the low half is folded into the high half. 12345678h + 9ABCDEF0h adds low halves 5678h+DEF0h and high halves 1234h+9ABCh through the carry, giving ACF13568h. Explained in: 8086 Assembly Program to Add Two 32-bit Numbers.

22. What goes wrong if you use ADD instead of ADC on the high word of a 32-bit addition?

The carry out of the low-word addition is simply lost, so the high word comes out one too small whenever the low half actually carried. For the same operands above, using ADD on both halves gives a high word of ACF0h instead of the correct ACF1h — a one-count error that is easy to miss because the low word is completely correct and the mistake only shows up in the high word, on inputs where the low half happens to carry. Explained in: 8086 Assembly Program to Add Two 32-bit Numbers.

23. How does MUL work, and how do you know from the flags whether the result overflowed one register?

MUL is unsigned multiply. The 8-bit form multiplies AL by an 8-bit operand into AX; the 16-bit form multiplies AX by a 16-bit operand into DX:AX, with DX holding the high 16 bits of the product. 1234h * 5678h = 06260060h, so DX ends up 0626h and AX 0060h. CF and OF are both set together, and both mean the same thing for MUL: the upper half (AH for the 8-bit form, DX for the 16-bit form) is not all zero, so the result did not fit in the lower register alone. Explained in: 8086 Assembly Program to Multiply Two 16-bit Numbers.

24. How is IMUL different from MUL?

IMUL is the signed counterpart: it treats both operands as two’s-complement and sign-extends the product across the full double-width result. AX = -2, multiplied by 3 with IMUL BX, gives DX:AX = FFFFFFFAh, which is -6 sign-extended to 32 bits — not the same bit pattern MUL would produce for the unsigned interpretation of the same register contents. Reach for IMUL the moment either operand can be negative; using MUL on signed data silently produces the wrong magnitude. Explained in: 8086 Assembly Program to Multiply Two 16-bit Numbers.

25. How does DIV work, and what has to be true about DX before you use it?

The 16-bit form divides the 32-bit value in DX:AX by a 16-bit operand, leaving the quotient in AX and the remainder in DX; the 8-bit form divides AX by an 8-bit operand, leaving the quotient in AL and the remainder in AH. DX has to hold the correct high half of the dividend before you divide, which for an unsigned value under 65,536 means explicitly clearing it (XOR DX, DX or MOV DX, 0) rather than leaving whatever a previous instruction left there. Explained in: 8086 Assembly Program to Divide Two 16-bit Numbers.

26. What actually happens if you forget to clear DX before a 16-bit DIV?

One of two things, and neither is a compile error, which is what makes it dangerous. If the stale value in DX makes the true dividend too large for the quotient to fit in AX, the 8086 raises interrupt 0 (divide error) and the program traps. But if the stale value is small enough that the quotient still fits, the division simply runs on the wrong dividend and returns a wrong answer with no complaint at all — dividing 4444h by 2 with DX cleared correctly gives 2222h, and the identical instructions with a leftover DX=1 silently give A222h instead, which looks like a perfectly ordinary result unless you already know what the right one was. Explained in: 8086 Assembly Program to Divide Two 16-bit Numbers.

27. What is DAA, and why does BCD arithmetic need it at all?

Packed BCD stores one decimal digit per nibble, so ADD on two BCD bytes is really an ordinary binary addition that has no idea the nibbles are supposed to stay in the range 0–9. 39h + 48h (representing the decimal digits 3 9 and 4 8) is 81h in binary, and DAA (decimal adjust after addition) corrects that binary result back into valid packed BCD using AF and CF, turning 81h into the correct 87h, which is 39 + 48 = 87 read as decimal digits. Explained in: 8086 Assembly Program to Convert Binary Number into BCD Format.

28. What do AAA and AAM do, and how are they different from DAA?

DAA adjusts packed BCD, where two digits share one byte; AAA and AAM work on unpacked BCD, where each byte holds one digit in its low nibble. AAM follows a MUL of two single unpacked digits and splits the binary byte in AL back into two unpacked decimal digits, tens in AH and units in AL — 7 * 9 = 63 in AL becomes AH=06, AL=03 after AAM. AAA follows an ADD of two ASCII digit characters and corrects AL (using AF) so that, after masking off the top nibble, it holds the correct decimal digit and carries a 1 into AH when the addition rolled over 9. Explained in: 8086 Assembly Program to Convert Binary Number into BCD Format.

29. What are the 8086’s flags, and which instructions actually change them?

Nine flags: three control flags the programmer sets directly (TF for single-step trace, IF for the interrupt-enable, DF for string direction) and six status flags that instructions set as a side effect (CF carry, PF parity, AF auxiliary carry, ZF zero, SF sign, OF overflow). The arithmetic instructions — ADD, SUB, ADC, SBB, CMP, NEG, MUL, IMUL, DIV, IDIV — are the ones that update the status flags based on their result; data-movement instructions such as MOV, PUSH, POP, LEA and XCHG leave every flag untouched. Explained in: 8086 Flag Register: All 9 Flags, Conditional Jumps, and Critical Traps.

30. What is the actual difference between the carry flag and the overflow flag?

CF flags an unsigned overflow: the true result needed one more bit than the register has, so reading the bits back as an unsigned number is wrong. OF flags a signed overflow: the true result crossed outside the range a two’s-complement value of that width can represent, so reading the bits back as a signed number is wrong. The two are independent and an operation can set either, both or neither — 0xFF + 0x01 in an 8-bit register gives 00h with CF=1 (256 does not fit unsigned) but OF=0 (as signed numbers, -1 + 1 = 0 is exactly correct); 0x7F + 1 gives 80h with CF=0 (128 fits fine unsigned) but OF=1 (127 + 1 overflowed into the negative range); 0x80 + 0x80 sets both at once. Explained in: 8086 Flag Register: All 9 Flags, Conditional Jumps, and Critical Traps.

31. Why do INC and DEC leave the carry flag alone, when ADD and SUB by 1 do not?

It is a deliberate design choice, not an inconsistency: INC and DEC update SF, ZF, AF, PF and OF, but they never touch CF. That lets a loop that uses INC or DEC on a running total keep a carry produced earlier in the same sequence alive across it, which is exactly what a multi-word arithmetic loop needs — if INC cleared CF the way ADD ..., 1 does, you could not increment a loop counter in the middle of propagating a carry through a chain of ADCs. Explained in: 8086 Flag Register: All 9 Flags, Conditional Jumps, and Critical Traps.

32. How do AND, OR, XOR, TEST and NOT affect the flags?

AND, OR, XOR and TEST always clear CF and OF (a bitwise operation cannot overflow in either sense), and they set SF, ZF and PF from the result the normal way; TEST computes the same flags as AND but discards the result, exactly the way CMP discards a subtraction. NOT is the odd one out: it affects no flags whatsoever, because a bitwise complement produces no result condition worth reporting. Explained in: 8086 Assembly Program for Bitwise Operations: AND, OR, XOR, and NOT.

Control flow, the stack and subroutines

This section is where a viva usually turns from definitions into a trace: draw the stack, show what a specific instruction does to SP, and say what breaks if you get the counter register wrong.

33. What is the difference between JG and JA, and why does the 8086 need two different “jump if greater” instructions?

Both follow a CMP and both mean “jump if the first operand was bigger”, but they read the flags differently because “bigger” means something different for signed and unsigned data. JA/JB (and JAE/JBE) are the unsigned family and test CF and ZF. JG/JL (and JGE/JLE) are the signed family and test whether SF equals OF, together with ZF. Comparing AL=0x7F against 0x80 (127 against −128 if signed, 127 against 128 if unsigned) sets CF=1 and SF≠OF is false, so JB is taken (unsigned: 127 is below 128) while JG is also taken (signed: 127 is greater than −128) — the identical CMP supports two opposite-sounding correct answers, and picking the wrong jump family for the data’s actual sign is one of the most common exam and real-world bugs. Explained in: 8086 Flag Register: All 9 Flags, Conditional Jumps, and Critical Traps.

34. What does LOOP actually do to CX, and what happens if CX starts at zero?

LOOP label decrements CX by 1 and jumps to label if the result is not zero; if CX reaches zero it falls through. The trap is what happens when CX is 0 going in: LOOP decrements first, so 0 becomes FFFFh (65,535), which is not zero, so it jumps — and the loop body ends up running the full 65,536 times before CX finally counts back down to 0, instead of the 0 times a novice expects. JCXZ label is the instruction that actually tests “is CX zero” without decrementing anything, and the standard guard is to put it before the loop so a genuinely empty case skips the body instead of running it 65,536 times. Explained in: 8086 Assembly Program to Calculate the Factorial of an Integer Using Loops and Registers.

35. How does the stack grow on the 8086, and what does SS:SP actually point at?

The stack grows downward: SP holds the offset of the last word pushed, relative to SS, and every PUSH moves SP toward lower addresses before writing, while every POP reads first and then moves SP toward higher addresses. MOV SP, 0x100 sets an empty stack whose next PUSH will land at 0xFE, then 0xFC, and so on — the stack segment does not grow with use, it is a fixed region and SP simply walks down through it. Explained in: 8086 Stack Operations: SS:SP, PUSH/POP, CALL/RET, and Stack Frames.

36. Exactly what bytes does PUSH write, and in what order?

PUSH decrements SP by 2 first, then writes the full 16-bit value at the new SS:SP as two bytes, low byte at the lower address, exactly like any other 8086 word store. Starting from SP=0x100, PUSH AX (AX=1234h) followed by PUSH BX (BX=5678h) leaves 0xFC holding 78h, 0xFD holding 56h, 0xFE holding 34h, 0xFF holding 12h, and SP at 0xFC — the two pushes moved SP down by 4 total, and a subsequent POP CX then POP DX reads the words back in the reverse order they went in, giving CX=5678h (BX’s value, pushed last) and DX=1234h (AX’s value, pushed first). Explained in: 8086 Stack Operations: SS:SP, PUSH/POP, CALL/RET, and Stack Frames.

37. What exactly does CALL push, and what is the difference between RET and RET n?

A near CALL pushes the 16-bit offset of the instruction following it (the return address) and then jumps; a bare RET pops that word back into IP and continues there. RET n does the same pop and then adds n to SP, which is how a callee discards parameters the caller pushed before the call, instead of leaving the caller to clean up its own stack. CALL p ... p: RET 4 after two prior word pushes leaves SP back where it was before either the pushes or the call — the two pushed words are gone along with the return address slot, in one instruction. Explained in: 8086 Assembly: PUSH, POP, CALL, and RET – Stack Operations Explained.

38. What is the difference between a near and a far CALL or RET?

A near CALL/RET pair only ever touches IP, because the routine lives in the same code segment as the caller; a far CALL/RET pair pushes and pops both CS and IP, because the target is in a different segment. The encodings differ too: near CALL is opcode E8h and near RET is C3h (C2h for RET n), while far CALL is 9Ah and far RET is CBh. Mixing them — a near CALL matched with a far RET, say — pops the wrong number of bytes and hands control to garbage; there is no way for the CPU to notice the mismatch on its own. Explained in: 8086 Stack Operations: SS:SP, PUSH/POP, CALL/RET, and Stack Frames.

39. How do you build a stack frame so a subroutine can read its caller’s parameters?

The standard prologue is PUSH BP (save the caller’s frame pointer) then MOV BP, SP (make BP the new frame pointer); the standard epilogue is POP BP then RET. Once BP is set, the two words already on the stack — the saved BP at [BP] and the near return address at [BP+2] — sit below whatever the caller pushed before the CALL, so the first parameter the caller pushed is at [BP+4] and the second at [BP+6]. A routine that adds two words pushed by its caller reads them as [BP+4] and [BP+6] and returns the sum in AX, and this is exactly why [BP] defaults to the stack segment: the whole point of BP is addressing this frame. Explained in: 8086 Assembly: PUSH, POP, CALL, and RET – Stack Operations Explained.

40. How does a recursive subroutine work on the 8086, given that there is only one hardware stack?

Recursion just means the same subroutine issues another CALL to itself before returning, and every CALL pushes one more return address onto the single stack, so the depth of recursion is limited only by how much stack space is available. The published factorial-by-recursion program pushes CX before the recursive call and pops it back afterward so that each level’s own copy of the counter survives the call underneath it; factorial(5) correctly returns 78h (120) with SP back at its starting value once every level has returned, which is the check that no level leaked a stack slot. Explained in: 8086 Assembly Program to Compute Factorial of an Integer Using Recursion.

41. Why does an assembler reject PUSH 20, when PUSH AX and PUSH BX are fine?

Pushing an immediate value directly, without first loading it into a register, was only added in the 80186; the real 8086 instruction set has no encoding for it at all, so an assembler targeting the 8086 (rather than a later, backward-compatible chip) rejects PUSH 20 outright. The same generation gap catches a handful of other convenient-looking instructions: SHL/SHR/SAR with an immediate shift count above 1, IMUL with an immediate third operand, and PUSHA/POPA and ENTER/LEAVE are all 186-and-later additions that a strict 8086 assembler will not accept, even though many textbooks show them without comment. Explained in: 8086 Stack Operations: SS:SP, PUSH/POP, CALL/RET, and Stack Frames.

42. A published power-by-repeated-multiplication routine used POP CX inside its own loop and got the wrong answer — why?

The routine needed the top half of each 32-bit partial product off the stack, so it pushed DX and later popped it back into a register to add into the running total — but it used POP CX to do it, and CX was the very register the surrounding LOOP instruction was counting down. Every pass through the loop silently overwrote the iteration counter with whatever had been pushed that pass, so LOOP was no longer counting the number of multiplications at all, and 3^4 did not come out to 81. Moving the iteration counter to a register nothing else in the loop body touches (DI, decremented and tested with DEC/JNZ instead of LOOP) fixes it and gives the correct 81 for 3^4, and the correct 32-bit 3486784401 once the exponent is large enough that the result needs both words. Explained in: 8086 Assembly Program to Compute the Power of a Number.

43. How do a linear search, a bubble sort and an array reversal put these pieces together?

All three are the same handful of tools in different combinations: SI or DI walking an array one element at a time, CX (or CH/CL for a nested pass) counting how many comparisons remain, CMP followed by a conditional jump deciding whether to act, and INC/DEC advancing the pointer or the counter without disturbing a carry in flight. A linear search increments SI and decrements CX until a CMP matches or CX hits zero; a bubble sort nests an outer pass count in CH inside an inner pass count in CL and swaps adjacent bytes whenever a CMP finds them out of order — sorting 99 12 56 45 36 this way correctly yields 12 36 45 56 99; a reversal walks one index up from the start while writing to an index counting down from the end. None of the three needs an instruction that has not already appeared in this section. Explained in: 8086 Assembly Program to Sort Numbers in Ascending Order.

Interrupts and the interrupt vector table

Interrupts are usually the last topic covered and the first one forgotten, which makes this section disproportionately valuable for a viva — six focused questions cover most of what gets asked.

44. What actually happens on the CPU when an INT n instruction executes?

INT n pushes the current FLAGS, then CS, then IP, in that order, onto the stack (three words, six bytes), clears IF and TF so the handler cannot be interrupted again or single-stepped by accident, and then loads CS:IP from the interrupt vector table entry for number n. Execution continues at whatever address that entry names, exactly as if a far call had jumped there — except a far call never touches FLAGS, and INT always does. Explained in: 8086 Interrupt System: IVT, ISR Writing, and Hardware Interrupts.

45. What is the interrupt vector table, and how do you find the entry for a given interrupt number?

The IVT is a fixed table at the very bottom of memory, physical addresses 00000h through 003FFh, holding 256 four-byte entries, one per possible interrupt number 0–255. Entry n sits at physical address n * 4; the first word there is the handler’s offset and the second word is its segment. INT 21h (33 decimal) looks up address 33 * 4 = 84h, and INT FFh looks up 255 * 4 = 3FCh, right at the very end of the table’s 400h (1,024) bytes.
What INT n and IRET actually push and pop Before INT n FLAGS CS IP (caller’s stack) INT n executes push FLAGS push CS push IP clear IF, TF load CS:IP from IVT entry n×4 Handler runs stack now holds IP CS FLAGS (top to bottom) ends with IRET IRET pop IP, pop CS, pop FLAGS (exact reverse of INT) INT pushes three words and clears IF and TF on the way in; IRET pops the same three words on the way out, which is why a plain RET after an interrupt handler leaves FLAGS stranded on the stack.
The figure is the full round trip: three words go on the stack and IF/TF get cleared on the way into the handler, and IRET below is the only instruction that reverses all three on the way out. Explained in: 8086 Interrupt System: IVT, ISR Writing, and Hardware Interrupts.

46. What is the difference between INT 21h and INT 3h?

INT 21h is MS-DOS’s software interrupt: AH selects which DOS service you want before the interrupt fires (AH=09h to print a $-terminated string, AH=4Ch to terminate the program, and dozens more), so the same instruction does completely different things depending on what you loaded into AH beforehand. INT 3h is the dedicated one-byte breakpoint interrupt (opcode CCh) that debuggers plant over an instruction to stop execution there; it takes no service number and does one thing only. The two live at completely different, unrelated IVT slots and have nothing to do with each other beyond both being reached through the same INT mechanism. Explained in: Understanding INT 3h vs INT 21h in 8086 Assembly.

47. Are INT 3 and the one-byte breakpoint opcode the same instruction?

Not quite, and it is a genuinely easy trap to fall into while typing source by hand. The dedicated single-byte breakpoint form, INT3, assembles to exactly one byte, CCh, and that is the opcode debuggers actually splice into running code. Typing the general two-operand form, INT 3, assembles instead to the general INT n encoding with n=3, two bytes, CD 03h. Both eventually vector through IVT entry 3, but they are different byte sequences, and a debugger’s own breakpoint-restore logic specifically expects the one-byte CCh form. Explained in: Understanding INT 3h vs INT 21h in 8086 Assembly.

48. How are hardware interrupts different from the software INT instruction, and what does IF control?

A software interrupt is an instruction the running program executes on purpose. A hardware interrupt is a signal from outside the CPU — a timer, a keyboard controller, a disk controller — arriving on the INTR pin (or the non-maskable NMI pin), which an interrupt controller chip turns into a specific interrupt number for the CPU to vector through, the same IVT mechanism either way. IF, the interrupt flag, is the switch for the maskable kind: STI sets it (hardware interrupts are accepted) and CLI clears it (they are held off); NMI ignores IF entirely, which is what “non-maskable” means. INT n executed deliberately by a program is never affected by IF at all. Explained in: 8086 Assembly: Handling the External Timer Interrupt (INT 08h).

49. What does IRET do that a plain RET does not, and why does an interrupt handler need it?

IRET pops IP, then CS, then FLAGS, in the reverse order INT pushed them — three words, restoring not just where execution resumes but the exact flag state from before the interrupt, including IF and TF. A plain near RET only pops IP; a far RET pops IP and CS but never touches FLAGS. Ending a handler with an ordinary RET leaves the stack one word short (FLAGS is still sitting there) and leaves the flags however the handler last left them, both of which corrupt the program the interrupt interrupted — IRET is not a style preference, it is the only correct way back from an INT. Explained in: 8086 Interrupt System: IVT, ISR Writing, and Hardware Interrupts.

Data directives, DOS and BIOS services, and the assembler itself

The last section is the one that separates “can write a program from a textbook example” from “understands what the assembler is actually doing with each line”, and it is where a surprising number of easy marks are lost.

50. What is the difference between DB, DW, DD, DQ and DT?

They are the data-definition directives, and the only thing that changes between them is how many bytes each one reserves per item: DB one byte, DW one word (2 bytes), DD one doubleword (4 bytes), DQ one quadword (8 bytes), and DT ten bytes, most often used for the extended-precision or packed-BCD values the floating-point instructions and some BCD routines expect. count DB 10 and count DW 10 both store the value ten, but the first uses one byte of memory and the second uses two, which matters the moment something else addresses the byte right after it. Explained in: 8086 Data Directives: DB, DW, DD, DQ, DT, DUP, EQU, PTR, and OFFSET.

51. What does DUP do, and why is buffer DB 20 DUP(0) different from writing out twenty zeros by hand?

DUP tells the assembler to repeat whatever is inside the parentheses a given number of times when it lays out the data, so buffer DB 20 DUP(0) reserves 20 zero-initialised bytes under the one label buffer without the source listing all twenty. It is purely an assemble-time convenience for the source file; the bytes it produces are identical to writing DB 0,0,0,... twenty times, and DUP can nest and can repeat something other than a single constant, such as a short repeating pattern. Explained in: 8086 Data Directives: DB, DW, DD, DQ, DT, DUP, EQU, PTR, and OFFSET.

52. What is the difference between EQU and defining a variable with DB or DW?

EQU creates a named constant purely for the assembler’s own bookkeeping: it is text substitution at assemble time, it occupies no memory, and a program cannot read it or write to it at run time, only refer to its value while assembling. DB/DW/etc. reserve actual bytes in the program’s data area, give them an address, and the running program can load from and store to that address like any other variable. SIZE EQU 10 and count DW 10 might both let you write SIZE or count in later instructions, but only count is a real memory location the CPU can touch while running. Explained in: 8086 Data Directives: DB, DW, DD, DQ, DT, DUP, EQU, PTR, and OFFSET.

53. What does the PTR operator do, and when do you actually need it?

PTR overrides the operand size the assembler would otherwise have to guess, which matters whenever an instruction’s memory operand has no register on the other side to infer the width from. MOV [BX], 5 is genuinely ambiguous — is that a byte 5 or a word 5? — and most assemblers refuse to guess; MOV BYTE PTR [BX], 5 or MOV WORD PTR [BX], 5 states it explicitly. When one side of the instruction is already a sized register, as in MOV AL, [BX] or MOV AX, [BX], the size is already unambiguous and PTR is not needed. Explained in: 8086 Data Directives: DB, DW, DD, DQ, DT, DUP, EQU, PTR, and OFFSET.

54. What does OFFSET give you, and how is it different from referring to a variable directly?

OFFSET variable is an assemble-time constant: the offset part of that label’s address, as a plain number you can load into a register with an ordinary MOV, for example MOV BX, OFFSET buffer to get buffer’s address into BX for later indexing. Referring to the label directly in most contexts instead reads or writes the value stored at that address. The distinction is the same one that separates LEA from a dereferencing MOV: one gives you the address, the other gives you what is there. Explained in: 8086 Data Directives: DB, DW, DD, DQ, DT, DUP, EQU, PTR, and OFFSET.

55. How do you print a string to the screen using a DOS call, and why does the string need a $ at the end?

AH=09h followed by INT 21h is the DOS “print string” service; DX must point at the string before the interrupt fires. The service does not take a length — it prints bytes starting at DS:DX until it finds a $ (24h) byte, which is the terminator this specific call looks for, not a null byte the way C strings work. Leaving the $ off means the call keeps printing whatever bytes happen to sit in memory after the string until it eventually stumbles on one by accident, which is the classic symptom of garbage trailing a printed line. Explained in: 8086 Assembly Program to Print ‘hello’ using 09H.

56. What is the difference between calling DOS (INT 21h) and calling the BIOS (INT 10h) to do screen output?

INT 21h is a DOS service and DOS itself is layered on top of the BIOS, so a DOS call to print a character eventually goes through the same hardware the BIOS talks to, just with DOS’s own buffering, redirection and $-terminated conventions in front of it. INT 10h calls the BIOS video services directly, with AH selecting the function — setting a video mode, placing the cursor, writing a character with a specific attribute (colour) at the current position — and skips DOS’s layer entirely, which is why BIOS video calls remain the way to do things (like coloured text) that the DOS string-printing call has no concept of. Explained in: Interrupting BIOS with 8086 Assembly Program.

57. What is the difference between the fixed-port and variable-port forms of IN and OUT?

Both instructions move one byte or word between AL/AX and an I/O port, but the port number is supplied two different ways. IN AL, 60h and OUT 60h, AL use an immediate 8-bit port number written directly in the instruction, which only reaches ports 0–255. IN AL, DX and OUT DX, AL take the port number from DX instead, which is a full 16-bit register, so it is the only form that can reach a port above 255; this is also why DX is the register the instruction set special-cases for wide port numbers rather than allowing a 16-bit immediate. Explained in: Implementing JUMP, PUSH, POP, IN & OUT in Assembly Program on 8086.

58. How do you read a single keystroke through the BIOS instead of through DOS?

INT 16h is the BIOS keyboard service; its AH=00h function waits for a key to be pressed and returns with the key’s ASCII code in AL and its hardware scan code in AH, bypassing DOS entirely the same way INT 10h bypasses it for video. Reading a key this way, rather than through a DOS console call, is the usual reason a program uses the BIOS interrupt directly — it works even in the simplest bare-metal or DOS-less test setup, since it only depends on the BIOS having initialised the keyboard controller, not on DOS being loaded at all. Explained in: Interrupting BIOS with 8086 Assembly Program.

59. Which 8086-looking instructions does a strict 8086 assembler actually reject, and why does that matter for a viva?

PUSH imm, SHL/SHR/SAR reg, imm for an immediate count greater than 1, three-operand IMUL, and PUSHA/POPA/ENTER/LEAVE all look like ordinary 8086 instructions in a textbook but were only added starting with the 80186; assembling any of them with the assembler restricted to the 8086 instruction set fails at assemble time rather than producing a working program. It matters for a viva because an examiner who asks “is this valid on the 8086” about one of these forms is testing whether the student can tell the difference between what their tool accepts by default and what the processor named in the syllabus actually implements. Explained in: The Complete 8086 Register Reference: AX, BX, CX, DX, Segment, Index & Pointer Registers Explained.

60. What is a “Mix” (C++ and Assembly) program, and why do the same exercises appear twice on this blog?

A mix program keeps the control logic in C++ and drops into inline assembly, historically inside Turbo C++’s asm { } blocks, for the parts an exercise wants demonstrated at the instruction level — finding the largest of a set of numbers, say, or sorting one. It is a different skill from a pure .asm program run under an assembler and DOSBox: the mix version has to respect the C++ calling and register-preservation conventions around the inline block, while the pure-assembly version owns the whole machine and can use any register for anything. Many exercises on this blog exist in both forms for exactly that reason — the pure-assembly post and its mix counterpart solve the same problem but are answers to two different questions, and a course that assigns both wants to see that you can do the logic either way. Explained in: Mix (C++ and Assembly) Program to Find Largest Number from Given Numbers.

Should you memorise the answers?

Should you memorise all sixty? No — and trying to is how a Sunday evening gets wasted on the wrong ten questions. An examiner who asks about DIV wants to hear that DX has to be cleared first and what happens if you forget, not a recitation of the mnemonic’s syntax. If you only have an hour, read questions 5, 6, 20, 22, 25, 26, 33, 34 and 45 first — the physical-address formula, the ADD-instead-of-ADC bug, the DIV/DX trap, signed-versus-unsigned jumps, the LOOP/CX=0 trap, and the INT/IRET round trip explain more of the syllabus than any other handful, and several of the remaining fifty-one questions build directly on one of those nine. Then work section by section and follow the Explained in or Worked program link under any answer that is not already familiar from writing the program yourself.

Further reading

FAQs

Is this for MASM, TASM, NASM or emu8086?

The mnemonics and behaviour are the 8086 instruction set itself, which every one of those assemblers targets the same way; the differences between them are almost entirely in directives and pseudo-ops (EQU, segment declarations, how you tell the assembler which instruction subset to accept), not in what an instruction does once assembled. Question 53 covers EQU specifically because it is one of the few places assembler dialects genuinely diverge in spelling, if not in concept.

Do I need to know exact clock cycle counts for a viva?

Rarely, and this page deliberately leaves them out. Cycle counts vary with the 8086’s bus state and the addressing mode used, they are not something an emulator built on a modern CPU running in real mode can measure honestly, and most syllabi ask for correctness and mechanism — what an instruction does to registers, flags and memory — rather than a specific timing number. If your syllabus does require timing tables, treat them as a separate memorisation exercise from everything on this page.

Why do some answers say “Worked program” and others say “Explained in”?

A Worked program link points at a full assembly program on this blog that this exact question is about — the factorial routine, the sort, the search. An Explained in link points at one of the six reference posts (architecture, registers, segmentation, addressing modes, stack, flags, interrupts, data directives) where the underlying mechanism is covered on its own, independent of any one program.

How was this page checked?

It has no companion repository, because it is a Q&A summary rather than a single project. Every numeric claim on this page — every register value, flag state, physical address and byte count — was independently assembled with NASM restricted to the 8086 instruction set (cpu 8086, which rejects 80186-and-later forms the same way a strict 8086 assembler would) and executed in a 16-bit real-mode Unicorn CPU emulator, then read back from the emulator’s own registers and memory rather than typed from memory. That harness is not a real 8086 — it runs on a modern x86 core in real mode — so it was used only for arithmetic, flags, addressing and control flow, never for anything that depends on real 8086 bus timing, pin behaviour, or hardware-specific quirks such as PUSH SP‘s documented 8086-only value. The original DOS-and-emulator sessions behind the linked programs were not re-run for this page.

Conclusion

Sixty questions come down to a short list of ideas that repeat under different names: a physical address is always segment shifted and added to offset, arithmetic instructions maintain flags that the branch and adjust instructions then read, the stack is one shared region that PUSH, CALL and INT all write to in the same downward direction, and an interrupt is a call that also saves and restores the flags. If you can trace those four ideas through a program by hand, most individual questions answer themselves, and the ones that do not are exactly the ones worth following the linked program for.

No Comments yet!

Leave a Reply

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