Implementing JUMP, PUSH, POP, IN & OUT in Assembly Program on 8086

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


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.

💡 Side note — PUSH/POP order is LIFO: The stack is Last-In, First-Out. The code pushes AX first, then BX, then CX. To restore them correctly you must pop in reverse: CX first, then BX, then AX. If you pop in the same order you pushed, every register gets its neighbour’s value. This program pushes and pops in the correct inverse order, so the registers are restored unchanged.

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.

⚠️ Common mistake — expecting consistent IN/OUT results: 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.


See Also

Leave a Reply

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