ข้อ 3 · 20 คะแนน

เขียนโค้ด Z80 Assembly

อาจารย์ทิ้งจุดไข่ปลาไว้ (“จงเขียนโค้ด Z80 Assembly ……….”) แปลว่าโจทย์จะผูกกับฮาร์ดแวร์ในข้อ 1 — และข้อ 4 กับข้อ 5 จะเอาโค้ดนี้ไปใช้ต่อ เพราะฉะนั้น เขียนโค้ดที่แตะทั้งหน่วยความจำและ I/O จะได้เดินต่อได้ทั้งสองข้อ

⚠ อย่าลืมอีกครึ่งหนึ่งของข้อนี้ — โปรแกรมหน่วงเวลา

ปีที่แล้วข้อ Assembly กับข้อ Delay เป็นคนละข้อกัน รวม 50 จาก 80 คะแนน และโจทย์ delay มาจาก Lab 3 ตรง ๆ — จุดไข่ปลาใน “จงเขียนโค้ด Z80 Assembly ……….” ของปีนี้จึงมีโอกาสสูงมากที่จะเป็นโจทย์นั้น

⏱ ไปหน้าโปรแกรมหน่วงเวลา — เฉลย 1 ms @ 2 MHz เต็ม ๆ

กลยุทธ์: เขียนโค้ดที่ “ให้ของ” กับข้อ 4 และข้อ 5

3.0 บรรทัดหัวโปรแกรม — cpu กับ hof คืออะไร มาจากไหน

ทุกโปรแกรมในใบงาน Lab เริ่มด้วยสองบรรทัดนี้เสมอ แต่ไม่มีใครอธิบายว่ามันคืออะไร

        cpu  "z80.tbl"
        hof  "INT8"
        org  2000h
        ... โปรแกรมของเรา ...
        end
มาจากไหน

มาจากตัวโปรแกรมแปลภาษาที่ใช้ในแลป — คำสั่ง C16 ที่รันใน DOSBox คือ Cross-16 Meta-Assembler เวอร์ชัน 1.21 ของบริษัท Universal Cross-Assemblers (ปี 1987)
ตรวจสอบจากไฟล์ C16.EXE ในโฟลเดอร์ z80 โดยตรง — ข้อความในไฟล์เขียนว่า “Cross-16 Meta-Assembler   PC/MS-DOS   Version 1.21 · Copyright (C) 1987 Universal Cross-Assemblers”

cpu "z80.tbl" — บอกว่าจะแปลเป็นภาษาซีพียูตัวไหน

คำว่า “Meta-Assembler” แปลว่าตัวมันเองไม่รู้จักคำสั่งของ CPU ตัวไหนเลย มันเป็นแค่โครงเปล่า ๆ ที่ต้องโหลดตารางชุดคำสั่งเข้ามาก่อนถึงจะแปลได้ — เขียน cpu "z80.tbl" ก็คือสั่งให้ไปอ่านไฟล์ Z80.TBL (อยู่ในโฟลเดอร์ z80 ที่ unzip มา) ข้างในหน้าตาแบบนี้

ADCA,(HL)^8E:
ADCA,A^8F:
ADCA,B^88:
ADDA,(HL)^86:
ADDA,A^87:
ADDA,B^80:
   ↑          ↑
mnemonic   opcode

ถ้าเปลี่ยนบรรทัดนี้เป็น cpu "6502.tbl" ตัวเดียวกันนี้ก็แปลภาษา 6502 ได้ทันที — นี่คือเหตุผลที่มันชื่อ Cross-16

hof "INT8" — บอกว่าจะออกไฟล์ผลลัพธ์เป็นรูปแบบไหน

HOF ย่อมาจาก Hex Output Format · INT8 คือ Intel HEX แบบ 8 บิต ซึ่งเป็นรูปแบบไฟล์ .h ที่โปรแกรม Z80Sim โหลดได้ด้วยคำสั่ง L

รูปแบบอื่นที่ Cross-16 รองรับ: BIN8 BIN16 INT8 INT16 MOT8 MOT16

ไฟล์ LAB1.H ที่ได้ออกมาหน้าตาแบบนี้

:0C2000003E0106020E0816038081827665
:00000001FF
ส่วนค่าความหมาย
::ตัวเริ่มเรคอร์ด
ByteCount0Cข้อมูล 12 ไบต์
Address2000เริ่มที่ 2000H (มาจาก org)
Type00เรคอร์ดข้อมูล
Data3E 01 06 02 …machine code ของโปรแกรม
Checksum65ไว้ตรวจว่าไฟล์ไม่เพี้ยน
แล้วในข้อสอบต้องเขียนไหม

เขียนไปด้วยจะดีกว่า — ใช้เวลาแค่ 2 บรรทัด และแสดงว่าเข้าใจว่าโปรแกรมจริง ๆ ต้องมีอะไรบ้าง ไม่ได้ท่องมาแค่ตัวคำสั่ง

แต่ต้องรู้ว่ามันไม่ใช่คำสั่ง — cpu, hof, org, end เรียกว่า assembler directive (คำสั่งสำหรับตัวแปลภาษา ไม่ใช่สำหรับ CPU) จึงไม่ถูกแปลเป็น machine code และไม่กินตำแหน่ง address
⇒ ในตาราง opcode ของข้อ 4 จะไม่มีบรรทัดพวกนี้ (ยกเว้น org ที่เขียนกำกับไว้ว่าตั้ง address ที่เท่าไร)

Directiveทำอะไรกิน address ไหม
cpu "z80.tbl"โหลดตารางชุดคำสั่งของ Z80ไม่
hof "INT8"ตั้งรูปแบบไฟล์ผลลัพธ์เป็น Intel HEX 8 บิตไม่
org 2000hตั้งตำแหน่งเริ่มต้นของโค้ดถัดจากนี้ไม่ แต่กำหนดว่า address เริ่มที่ไหน
endบอกว่าจบไฟล์ต้นฉบับแล้วไม่
equตั้งชื่อให้ค่าคงที่ เช่น PORT: equ 40hไม่
dfb / defbใส่ข้อมูลลงหน่วยความจำโดยตรง (ไม่ใช่คำสั่ง)กิน

3.1 Register set

ก่อนดูตาราง ให้ดูผังจริงก่อน — นี่คือหน้าตาที่ตำราและดาต้าชีตของ Zilog วาดไว้ ข้อสอบมักให้เขียนผังนี้จากความจำ หรือถามว่ารีจิสเตอร์ตัวไหนอยู่ตรงไหน

อ่านผังนี้ยังไงให้จำได้

ชุดสำรองทำงานยังไง — กดสลับดู

ปุ่มด้านล่างคือคำสั่งจริงสองคำสั่งที่มีในชุดคำสั่ง Z80 · สังเกตว่าค่าไม่ได้ถูกคัดลอก แต่สลับที่กัน กดซ้ำอีกครั้งค่าจะกลับมาเหมือนเดิม

ตารางสรุปรีจิสเตอร์

Register 8 บิต

ชุดหลักชุดสำรองใช้ทำอะไร
AA′Accumulator — ผลลัพธ์ของ ALU และเป็นตัวเดียวที่ใช้กับ IN/OUT แบบ (n)
FF′Flag register — ผลสถานะจาก ALU
B CB′ C′ทั่วไป · B เป็นตัวนับของ DJNZ · C เป็นเลขพอร์ตของ IN/OUT แบบ (C)
D ED′ E′ทั่วไป · คู่ DE ใช้เป็น pointer ปลายทางของ LDIR
H LH′ L′ทั่วไป · คู่ HL เป็น pointer หลัก ใช้กับ (HL)

สลับชุดด้วย EX AF,AF′ และ EXX — ใช้ตอนเข้า interrupt จะเร็วกว่า push/pop

Register 16 บิต และตัวพิเศษ

ชื่อบิตใช้ทำอะไร
BC DE HL16จับคู่จาก register 8 บิต
IX IY16Index register — ใช้กับ (IX+d) โดย d เป็น signed −128…+127
SP16Stack Pointer — ทำงานแบบ LIFO ใช้กับ PUSH/POP/CALL/RET/interrupt
PC16Program Counter — ชี้คำสั่งถัดไป · RESET แล้วเป็น 0000H
I8Interrupt vector register — ใช้เป็นไบต์สูงของ vector ใน interrupt mode 2
R8Memory Refresh — เพิ่มเฉพาะบิต 0–6 ทุก M1 บิต 7 คงเดิม

R เป็น register 8 บิต ไม่ใช่ 7 บิต — แค่การนับใช้แค่ 7 บิตล่าง (ดูหน้า สไลด์ผิด)

Flag register (F)

F เป็นรีจิสเตอร์ 8 บิตเหมือนตัวอื่น แต่แต่ละบิตมีความหมายเฉพาะตัว ไม่ได้เก็บตัวเลข — ALU จะเขียนค่าลงบิตเหล่านี้ให้อัตโนมัติทุกครั้งที่คำนวณเสร็จ แล้วคำสั่ง jump แบบมีเงื่อนไขจะมาอ่านต่อ

แต่ละธง “เป็น 1” กับ “เป็น 0” แปลว่าอะไร

ลองคำนวณดู — ใส่เลขแล้วดูว่าธงไหนขึ้นเพราะอะไร

เลือกคำสั่ง ใส่ค่า A และ n (เลขฐานสิบหก) แล้วดูค่าธงพร้อมเหตุผลของแต่ละตัว — ลองกดตัวอย่างสำเร็จรูปด้านขวาดูก่อนก็ได้

จุดที่ออกสอบบ่อยเรื่อง flag
บิตD7D6D5D4D3D2D1D0
ชื่อSZF5HF3P/VNC
ความหมายSign
(= บิต 7 ของผล)
Zero
(ผล = 0)
ไม่ใช้Half carry
(บิต 3→4)
ไม่ใช้Parity /
Overflow
Add /
Subtract
Carry
ทดสอบเงื่อนไขได้ไหมได้ (P/M)ได้ (Z/NZ)—ไม่ได้—ได้ (PE/PO)ไม่ได้ได้ (C/NC)

H และ N ใช้ภายในสำหรับคำสั่ง DAA เท่านั้น — เขียนเงื่อนไข jump จากสองตัวนี้ไม่ได้

3.2 Addressing mode ที่ต้องแยกให้ออก

โหมดตัวอย่างความหมาย
ImpliedHALT, CPLไม่มี operand หรือ operand ฝังอยู่ใน opcode
RegisterLD B,Aทำงานระหว่าง register ตรง ๆ เร็วที่สุด (1 byte, 4 T)
ImmediateLD A,9FHค่าคงที่ 8 บิตตามหลัง opcode
Immediate extendedLD HL,2100Hค่าคงที่ 16 บิต เก็บ ไบต์ต่ำก่อน
Extended (direct)LD A,(1000H)ระบุ address 16 บิตตรง ๆ
Register indirectLD A,(HL)ใช้ค่าใน register pair เป็น address
IndexedLD A,(IX+5)address = IX + displacement (signed 8 บิต) · ต้องมี prefix DD/FD
RelativeJR LOOPPC = PC + 2 + d โดย d เป็น signed −128…+127
BitSET 3,Aระบุหมายเลขบิตใน opcode · ต้องมี prefix CB
Little-endian — ผิดกันบ่อยมากในข้อ 4

Z80 เก็บค่า 16 บิตแบบ ไบต์ต่ำก่อน ไบต์สูงทีหลัง
LD HL,2100H → machine code 21 00 21 ไม่ใช่ 21 21 00
LD (1000H),A → 32 00 10

3.3 กลุ่มคำสั่งที่ใช้จริงในข้อสอบ

ย้ายข้อมูล

LD   A, B            ; register → register
LD   A, 9FH          ; ค่าคงที่ → register
LD   HL, 2100H       ; ค่าคงที่ 16 บิต
LD   A, (HL)         ; อ่านจาก address ใน HL
LD   (HL), A         ; เขียนไปที่ address ใน HL
LD   A, (1000H)      ; อ่านจาก address ตรง ๆ
LD   (1000H), A      ; เขียนไปที่ address ตรง ๆ
EX   DE, HL          ; สลับสองคู่
PUSH BC  /  POP BC   ; ผ่าน stack

คำนวณ

ADD  A, B       ; A ← A + B
ADC  A, B       ; บวกพร้อม carry
SUB  B          ; A ← A − B  (ไม่ต้องเขียน A,)
SBC  A, B       ; ลบพร้อม borrow
INC  A  / DEC A  ; +1 / −1 (ไม่กระทบ carry)
INC  DE            ; +1 กับคู่ 16 บิต
AND B / OR B / XOR B
CP   10H           ; เปรียบเทียบ (ลบทิ้ง เก็บแค่ flag)
CPL                ; กลับทุกบิตของ A
ADD  HL, DE       ; บวก 16 บิต

ควบคุมทิศทาง

JP   2000H        ; กระโดดแบบระบุ address
JP   NZ, 2000H    ; มีเงื่อนไข
JR   LOOP         ; กระโดดสั้น ±127 (2 byte)
DJNZ LOOP         ; B←B−1 แล้วกระโดดถ้า B≠0
CALL 3000H        ; เรียก subroutine
RET  / RET Z       ; กลับ
HALT               ; หยุด (ยังวน refresh อยู่)

ติดต่อ I/O

IN   A, (84H)     ; อ่านพอร์ต 84H เข้า A
OUT  (40H), A     ; ส่ง A ออกพอร์ต 40H
IN   B, (C)       ; เลขพอร์ตอยู่ใน C (2 byte, prefix ED)
OUT  (C), B       ; เลขพอร์ตอยู่ใน C

แบบ (n) ใช้ได้กับ A เท่านั้น ถ้าจะใช้ register อื่นต้องใช้แบบ (C) ซึ่งกิน 2 ไบต์และ 12 T

3.4 แพตเทิร์นสำเร็จรูป

อ่านพอร์ต → เก็บลง RAM → ส่งออกพอร์ต

        IN   A, (84H)      ; อ่าน DIP switch
        LD   (1000H), A    ; เก็บไว้ใน RAM
        CPL                 ; กลับบิตทุกตัว
        OUT  (40H), A      ; แสดงที่ LED

ลูปหน่วงเวลา

        LD   B, 0FFH        ; ตัวนับ 255 รอบ
DELAY:  DJNZ DELAY          ; วนจนกว่า B = 0

; เวลา = 255 รอบ × 13 T − 5 T (รอบสุดท้าย 8 T) = 3,310 T
; ที่ 4 MHz ≈ 827.5 µs

คัดลอกบล็อกข้อมูล

        LD   HL, 1000H      ; ต้นทาง
        LD   DE, 1100H      ; ปลายทาง
        LD   BC, 0010H      ; จำนวน 16 ไบต์
        LDIR                ; ทำซ้ำจนกว่า BC = 0

บวกเลขสองค่าจาก RAM (แบบใบงาน Lab 2)

        LD   HL, 2100H
        LD   DE, 2101H
        LD   A, (HL)        ; ตัวตั้ง
        LD   B, A
        LD   A, (DE)        ; ตัวบวก
        ADD  A, B
        INC  DE              ; ชี้ไป 2102H
        LD   (DE), A        ; เก็บผลลัพธ์
        HALT

3.5 ลองเขียนเอง — assembler สด

พิมพ์โค้ดในกล่องซ้าย ระบบจะแปลให้ทันที พร้อมบอก address, machine code, จำนวนไบต์, machine cycle และ T-state — ใช้ตรวจคำตอบข้อ 4 ได้เลย

โจทย์หลัก (คาดการณ์จากโครงข้อสอบ)

โจทย์หลัก · 20 คะแนน

จงเขียนโค้ด Z80 Assembly สำหรับระบบที่ออกแบบในข้อ 1 (ROM 4 KB ที่ 0000H, RWM 4 KB ที่ 1000H, พอร์ตอินพุต 84H, พอร์ตเอาต์พุต 40H) โดยให้โปรแกรมอ่านค่าจากสวิตช์ เก็บลงหน่วยความจำ แล้วนำค่ากลับมาแสดงที่ LED พร้อมทั้งอธิบายการทำงานของแต่ละคำสั่ง

ดูเฉลย
        CPU  "z80.tbl"      ; โหลดตารางคำสั่ง Z80 ให้ตัวแปลภาษา
        HOF  "INT8"          ; ให้ออกไฟล์เป็น Intel HEX 8 บิต
        ORG  0000H           ; RESET แล้ว PC = 0000H จึงต้องเริ่มที่นี่

START:  LD   SP, 1FFFH       ; ตั้ง stack ที่ท้าย RWM (โตลงล่าง)
        LD   HL, 1000H       ; HL ชี้ที่เก็บข้อมูลใน RWM

MAIN:   IN   A, (84H)        ; อ่าน 8 บิตจาก DIP switch เข้า A
        LD   (HL), A          ; เก็บลง RWM ที่ 1000H

        LD   A, (1000H)       ; อ่านกลับมาจาก RWM (ยืนยันว่าเขียนติด)
        CPL                   ; กลับทุกบิต (ตัวอย่างการประมวลผลก่อนแสดงผล)
        OUT  (40H), A        ; ส่งออกไปที่ latch ของ LED

        LD   B, 0FFH          ; หน่วงเวลาให้ตาเห็น
DELAY:  DJNZ DELAY

        JR   MAIN              ; วนอ่านสวิตช์ตลอดไป

        HALT
        END

อธิบายทีละคำสั่ง

คำสั่งทำอะไรmachine cycle
LD SP,1FFFHตั้ง stack pointer ไว้ปลายบนของ RWM เพราะ stack โตจากสูงไปต่ำ ถ้าไม่ตั้งค่า SP จะเป็น FFFFH ซึ่งไม่มี RAM อยู่จริงM1 + MR + MR
LD HL,1000Hให้ HL เป็น pointer ชี้ไปที่ RWMM1 + MR + MR
IN A,(84H)สร้าง I/O Read cycle · A0–A7 = 84H → SEL84 ทำงาน → 74LS244 ปล่อยค่าสวิตช์ขึ้น data bus → เข้า AM1 + MR + IOR
LD (HL),Aสร้าง Memory Write cycle ไปที่ 1000H → CERWM ทำงาน → MEMWR เขียนข้อมูลลงเซลล์M1 + MW
LD A,(1000H)สร้าง Memory Read cycle · อ่าน operand 2 ไบต์ก่อน (little-endian) แล้วอ่านข้อมูลจริงM1 + MR + MR + MR
CPLกลับทุกบิตของ A (one's complement) — ใส่ไว้เป็นตัวอย่างการประมวลผลก่อนแสดงผล · 1 ไบต์ 4 T ไม่แตะบัสเพิ่มM1
OUT (40H),Aสร้าง I/O Write cycle · A0–A7 = 40H, A8–A15 = ค่าใน A → IOWR clock ให้ 74LS273 เก็บค่า → LED ติดM1 + MR + IOW
DJNZ DELAYB ← B−1 แล้วกระโดดกลับถ้ายังไม่ 0 · M1 ของ DJNZ ยาว 5 T ไม่ใช่ 4 TM1(5) + MR (+5 T ถ้ากระโดด)
JR MAINกระโดดแบบ relative — เก็บ displacement เป็น signed 8 บิต ประหยัดกว่า JP 1 ไบต์M1 + MR (+5 T)

ตารางที่จะใช้ต่อในข้อ 4

จุดที่ทำให้ได้คะแนนเพิ่ม
  • ใส่ ORG 0000H และอธิบายว่าทำไมต้องเป็น 0000H (RESET แล้ว PC = 0000H)
  • ตั้ง SP ไว้ในช่วง RAM จริง และบอกว่า stack โตลงล่าง
  • ใส่ comment ทุกบรรทัด — ข้อสอบให้ “อธิบายการทำงาน” ด้วย
  • บอกด้วยว่าคำสั่งไหนสร้าง machine cycle ชนิดใด เพราะข้อ 5 จะเอาไปวาดต่อ

ตัวอย่างเพิ่มอีก 5 ข้อ

ตัวอย่างที่ 1

จงเขียนโปรแกรมที่อ่านค่า 2 ไบต์จาก 1000H และ 1001H นำมาบวกกัน แล้วเก็บผลที่ 1002H ถ้ามี carry ให้เก็บ 01H ที่ 1003H มิฉะนั้นเก็บ 00H

เฉลยตัวอย่างที่ 1
        CPU  "z80.tbl"
        HOF  "INT8"
        ORG  0000H
        LD   HL, 1000H
        LD   A, (HL)      ; A ← ตัวตั้ง
        INC  HL
        ADD  A, (HL)      ; A ← A + ตัวบวก (ตั้ง carry flag ให้ด้วย)
        INC  HL
        LD   (HL), A      ; เก็บผลที่ 1002H
        INC  HL
        LD   A, 00H        ; เตรียมค่า 0  (ห้ามใช้ XOR A เพราะจะล้าง carry)
        ADC  A, 00H        ; A ← 0 + 0 + carry  ⇒ ได้ 0 หรือ 1 พอดี
        LD   (HL), A      ; เก็บที่ 1003H
        HALT
กับดัก

XOR A เป็นวิธียอดนิยมในการเคลียร์ A (1 ไบต์ 4 T) แต่มันล้าง carry flag ทิ้ง ถ้าจะเก็บ carry ไว้ใช้ต่อ ต้องใช้ LD A,00H แทน

ตัวอย่างที่ 2

จงเขียนโปรแกรมนับไฟวิ่ง (LED เพิ่มค่าทีละ 1) ออกที่พอร์ต 40H โดยมีการหน่วงเวลาระหว่างรอบ และวนไม่รู้จบ

เฉลยตัวอย่างที่ 2
        CPU  "z80.tbl"
        HOF  "INT8"
        ORG  0000H
        XOR  A              ; A ← 0 (สั้นกว่า LD A,0 อยู่ 1 ไบต์)
LOOP:   OUT  (40H), A
        INC  A
        PUSH AF             ; เก็บ A ไว้ก่อน เพราะ DJNZ จะใช้ B
        LD   B, 0FFH
DLY:    DJNZ DLY
        POP  AF
        JR   LOOP
        END

เทียบกับใบงาน Lab 2 ที่ใช้ XOR A / OUT (40H),A / INC A / JR loop — โปรแกรมนี้เพิ่มการหน่วงเวลาเพื่อให้เห็นด้วยตาเปล่า และ push/pop เพื่อไม่ให้ B ไปทับค่าที่ต้องนับ

ตัวอย่างที่ 3

จงเขียนโปรแกรมที่รอจนกว่าจะมีการกดสวิตช์บิต 0 ของพอร์ต 84H (อ่านได้ 0 เมื่อกด) แล้วจึงส่งค่า 55H ออกพอร์ต 40H

เฉลยตัวอย่างที่ 3
        CPU  "z80.tbl"
        HOF  "INT8"
        ORG  0000H
WAITSW: IN   A, (84H)     ; อ่านสวิตช์
        BIT  0, A          ; ทดสอบบิต 0 → ผลไปที่ Z flag
        JR   NZ, WAITSW    ; ยังไม่กด (บิต = 1) → วนรอต่อ

        LD   A, 55H
        OUT  (40H), A
        HALT

BIT b,r ใช้ prefix CB — BIT 0,A = CB 47 (2 ไบต์, 8 T)

ทางเลือกที่ไม่ต้องใช้ CB: AND 01H แล้ว JR NZ — สั้นกว่า 0 ไบต์แต่ทำลายค่าใน A

ตัวอย่างที่ 4

จงเขียนโปรแกรมย้ายข้อมูล 16 ไบต์จาก ROM ตำแหน่ง 0100H ไปยัง RAM ตำแหน่ง 1000H สองแบบ: แบบใช้ลูปเอง และแบบใช้คำสั่งบล็อก แล้วเปรียบเทียบขนาดโค้ด

เฉลยตัวอย่างที่ 4

แบบลูปเอง

        LD   HL, 0100H
        LD   DE, 1000H
        LD   B, 16
CP1:    LD   A, (HL)
        LD   (DE), A
        INC  HL
        INC  DE
        DJNZ CP1
        HALT

แบบใช้คำสั่งบล็อก

        LD   HL, 0100H      ; ต้นทาง
        LD   DE, 1000H      ; ปลายทาง
        LD   BC, 0010H      ; จำนวน
        LDIR                ; ED B0 — ทำซ้ำเองจนกว่า BC = 0
        HALT
แบบขนาดT-state รวม (16 ไบต์)
ลูปเอง15 ไบต์10+10+7 + 16×(7+7+6+6+13) − 5 = 646 T
LDIR12 ไบต์10+10+10 + 15×21 + 16 = 361 T

(นับรวม HALT ทั้งสองแบบ) LDIR สั้นกว่า 3 ไบต์และเร็วกว่าเกือบเท่าตัว — แต่ต้องจำว่า LDIR ใช้ 21 T ต่อรอบ และรอบสุดท้าย (ตอน BC = 0) ใช้แค่ 16 T

ตัวอย่างที่ 5

จงเขียน subroutine ชื่อ OUTBYTE ที่รับค่าใน A แล้วส่งออกพอร์ต 40H พร้อมหน่วงเวลา และเขียนโปรแกรมหลักที่เรียกใช้ subroutine นี้ 3 ครั้งด้วยค่า 01H, 02H, 04H

เฉลยตัวอย่างที่ 5
        CPU  "z80.tbl"
        HOF  "INT8"
        ORG  0000H
        LD   SP, 1FFFH       ; ต้องตั้ง SP ก่อนใช้ CALL เสมอ!

        LD   A, 01H
        CALL OUTBYTE
        LD   A, 02H
        CALL OUTBYTE
        LD   A, 04H
        CALL OUTBYTE
        HALT

OUTBYTE: OUT  (40H), A
        PUSH BC              ; รักษาค่า B ของผู้เรียก
        LD   B, 0FFH
OB1:    DJNZ OB1
        POP  BC
        RET
        END
ข้อผิดพลาดที่เจอบ่อยที่สุด

ลืมตั้ง SP ก่อน CALL — CALL จะ push return address 2 ไบต์ลง stack ถ้า SP ยังเป็น FFFFH (ค่าหลัง reset จริง ๆ คือค่าไม่แน่นอน) จะเขียนลงที่ที่ไม่มี RAM พอ RET ก็จะกระโดดไปมั่ว โปรแกรมพัง

← ข้อ 2 · Foldback ข้อ 4 · Opcode →