This program is a sandbox for five instructions you’ll encounter constantly in real 8086 code: unconditional jump (JMP), stack push and pop, and port I/O (IN/OUT). Rather than demonstrating each in isolation, the program weaves them together into a two-iteration loop that modifies variables, saves and restores registers through the stack, then reads from and writes to an I/O port at the end. It’s a useful reference for anyone trying to understand how these instructions interact.
Prerequisites: Familiarity with 8086 registers and basic arithmetic instructions. Understanding 16-bit addition first is helpful.
data segment
abc dw 1101H
def dw 0025H
pqr dw 0011H
res dw ?
res1 dw ?
data ends
code segment
assume cs:code, ds:data
start:
mov ax, data
mov ds, ax
mov ax, abc ; AX = 1101H
mov bx, def ; BX = 0025H
mov cx, pqr ; CX = 0011H
mov dx, 0002H ; DX = loop counter
jmp ma ; skip 'back' block on first pass
back:
push ax ; save registers onto stack
push bx
push cx
pop cx ; restore in LIFO order (cx gets what was pushed last)
pop bx
pop ax
mov ax, abc ; reload abc, increment it
inc ax
mov abc, ax
mov bx, def ; reload def, decrement it
dec bx
mov def, bx
ma:
add ax, bx ; AX = AX + BX
mov res, ax ; store sum
dec dx
jnz back ; loop if DX != 0
in ax, 25H ; read from I/O port 25H
out 30H, ax ; write AX to port 30H
mov res1, ax ; store port value
int 3
code ends
end start
Related Links:
Tracing the execution
The program starts by loading abc=1101H, def=0025H, cx=0011H, and a loop counter dx=0002H. The jmp ma skips the stack block entirely on the first pass and lands directly at the addition.
Pass 1 (at label ma): add ax, bx gives 1101H + 0025H = 1126H. Stored in res. DX decremented to 1 — not zero, so jnz back fires.
Pass 2 (at label back): AX, BX, CX are pushed onto the stack, then immediately popped back in the same order. This is a no-op — the registers end up with exactly what they had before. Then abc is incremented to 1102H and def is decremented to 0024H. Execution falls through to ma again.
Back at ma: add ax, bx = 1102H + 0024H = 1126H. DX decremented to 0, jnz does not fire. Program falls through to in ax, 25H.
Output
C:TASM>debug pushpop.exe -g AX=1818 BX=0024 CX=0011 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000 DS=0B97 ES=0B87 SS=0B97 CS=0B98 IP=003B NV UP EI PL ZR NA PE NC 0B98:003B CC INT 3 -d 0B97:0000 0B97:0000 02 11 24 00 11 00 26 11-18 18 00 00 00 00 00 00 ..$...&......... -q
AX=1818H is the value returned from I/O port 25H — this is hardware-dependent and will vary between machines and emulators. BX=0024H shows def after the decrement on the second loop pass.
in ax, 25H reads from hardware I/O port 25H. In a real PC, this is the PIC (Programmable Interrupt Controller) mask register. In DOSBox or emu8086, the emulator decides what to return. You will likely see a different value from 1818H depending on your environment. Do not rely on port I/O for predictable values in a lab setting unless you know exactly what hardware is attached.
emu8086 version
Tested with: emu8086 v4.08, Windows 10.
; emu8086 -- JUMP, PUSH, POP, IN, OUT demonstration
#make_COM#
org 100h
start:
mov ax, word ptr abc
mov bx, word ptr def
mov cx, word ptr pqr
mov dx, 0002H
jmp ma
back:
push ax
push bx
push cx
pop cx
pop bx
pop ax
mov ax, word ptr abc
inc ax
mov word ptr abc, ax
mov bx, word ptr def
dec bx
mov word ptr def, bx
ma:
add ax, bx
mov word ptr res, ax
dec dx
jnz back
in ax, 25H
out 30H, ax
mov word ptr res1, ax
mov ax, 4c00h
int 21h
; --- Data ---
abc dw 1101H
def dw 0025H
pqr dw 0011H
res dw 0
res1 dw 0
NASM version
Tested with: NASM 2.16.01, DOSBox 0.74-3.
; NASM -- JUMP, PUSH, POP, IN, OUT demonstration
; nasm -f bin pushpop.asm -o pushpop.com
bits 16
org 100h
start:
mov ax, [abc]
mov bx, [def]
mov cx, [pqr]
mov dx, 0002H
jmp ma
back:
push ax
push bx
push cx
pop cx
pop bx
pop ax
mov ax, [abc]
inc ax
mov [abc], ax
mov bx, [def]
dec bx
mov [def], bx
ma:
add ax, bx
mov [res], ax
dec dx
jnz back
in ax, 25H
out 30H, ax
mov [res1], ax
mov ax, 4c00h
int 21h
; --- Data ---
abc dw 1101H
def dw 0025H
pqr dw 0011H
res dw 0
res1 dw 0
Frequently Asked Questions
Why does the program jump to ma instead of starting at back?
The back block is meant to execute between loop passes, not on the very first pass. If execution fell into back first, it would push and pop registers that haven’t been given meaningful values yet, then modify abc and def before the first addition has even happened. The jmp ma skips that setup block on the first pass and drops straight into the computation.
What does the PUSH/POP block actually do in this program?
Functionally, nothing — it saves and immediately restores the same values. It’s included to demonstrate stack mechanics: the three registers are saved to the stack in one order and retrieved in reverse, leaving each register with its original value. In a real program, you’d use PUSH/POP around a subroutine call to preserve registers that the subroutine might overwrite.
What are IN and OUT actually doing?
IN ax, 25H reads a 16-bit value from hardware I/O port 25H and puts it in AX. OUT 30H, ax writes AX to port 30H. On a real PC, port 25H is the Interrupt Mask Register of the first PIC chip. In an emulator, the returned value is whatever the emulator decides to simulate. These instructions exist because some devices — like sound cards, serial ports, and keyboard controllers — communicate via port I/O rather than memory-mapped registers.
Conclusion
This program packs five instructions into a single runnable demo: JMP skips the setup block on the first pass, PUSH/POP saves and restores registers in LIFO order, and IN/OUT reads and writes hardware ports. The most important takeaway is the stack order rule — always pop in the exact reverse of the push sequence. Get that wrong and registers silently receive each other’s values, producing bugs that are notoriously hard to diagnose.