ปีที่แล้วออกเป็นข้อ 2 เต็ม ๆ 20 คะแนน และมาจาก Lab 3 ตรง ๆ ปีนี้อาจารย์เขียนข้อ 3 ว่า “จงเขียนโค้ด Z80 Assembly ……….” — จุดไข่ปลานั้นมีโอกาสสูงมากที่จะเป็นโจทย์นี้ เพราะเป็นโจทย์เดียวที่ผูกกับ T-state ซึ่งเชื่อมไปข้อ 4 กับข้อ 5 ได้พอดี
“จงเขียนโปรแกรมภาษา Assembly ในการหน่วงเวลา (Delay) ขนาด 1 ms (10 คะแนน) โดยสมมติที่ความถี่ 2 MHz พร้อมแสดงวิธีการคำนวณมาอย่างละเอียด (10 คะแนน) (รวม 20 คะแนน)”
อาจารย์เคยโพสต์คำตอบของนักศึกษาคนหนึ่งไว้เป็นตัวอย่างเตือน พร้อมข้อความว่า
“เอาโปรแกรมในข้อ 1 มาเป็นคำตอบในข้อ 2 บอกว่าเป็น delay ขนาด 1ms …. มันใช่อยู่ดิ”
สิ่งที่เกิดขึ้นคือเขาลอกโปรแกรมบวก/ลบเลขจาก Lab 2 มาแล้วเขียนต่อท้ายว่า “delay 1 ms”
— เอามาให้ดูเพื่อจะได้เห็นชัด ๆ ว่าโจทย์ข้อนี้ต้องการอะไรที่โปรแกรมธรรมดาให้ไม่ได้
ld hl, 2100h
ld de, 2101h
ld a, (hl)
ld b, a
ld a, de ← คำสั่งนี้ไม่มีจริงด้วย
sub a, b
inc de
ld (de), a
delay 1 ms ← เขียนลอย ๆ ไม่มีการคำนวณ
ปัญหา: เป็นโปรแกรมลบเลขจาก Lab 2 ไม่มีลูป ไม่มีตัวนับ ไม่มีการคำนวณ T-state และรันจริงใช้เวลาไม่ถึง 30 µs
ld b, 99 ; ตัวนับรอบ
loop:
ld a, 1 ; ตัวถ่วงให้ลูปครบ 20 T
djnz loop ; วนจน B = 0
...
+ ตารางคำนวณ T-state ทุกบรรทัด
+ สมการรวม + แทนค่า + ตรวจคำตอบ
คะแนนครึ่งหนึ่ง (10 จาก 20) อยู่ที่วิธีคำนวณ ไม่ใช่ตัวโปรแกรม — เขียนโปรแกรมถูกแต่ไม่โชว์วิธีคิด ก็ได้แค่ครึ่งเดียว
Z80 ใช้เวลาทำงานแต่ละคำสั่งเป็นจำนวน T-state ที่แน่นอนและคาดเดาได้ (ชีท Instruction Set บอกไว้ทุกคำสั่ง) ถ้าเรารู้ความถี่ clock ก็แปลง T-state เป็นเวลาจริงได้
คาบของ 1 T-state T = 1 / f
จำนวน T ที่ต้องการ N = เวลาที่ต้องการ / T
= เวลาที่ต้องการ × f
หน่วยต้องตรงกัน — ถ้า f เป็น MHz ให้ใช้เวลาเป็น µs จะได้ตัวเลขสวย
| f | T = 1/f | 1 ms คิดเป็นกี่ T |
|---|---|---|
| 1 MHz | 1.0 µs | 1,000 T |
| 2 MHz | 0.5 µs | 2,000 T |
| 2.5 MHz | 0.4 µs | 2,500 T |
| 4 MHz | 0.25 µs | 4,000 T |
| 6 MHz | 0.1667 µs | 6,000 T |
| 8 MHz | 0.125 µs | 8,000 T |
เห็นแพตเทิร์นไหม: N = เวลาเป็น µs × f เป็น MHz ตรง ๆ
เทคนิคคือ ใส่คำสั่งที่ไม่ทำอะไรมีสาระเข้าไปเพื่อถมเวลาให้ครบพอดี เลือกจากตารางนี้ (ทุกตัวมีในชีท Instruction Set ที่อาจารย์แจกให้)
| คำสั่ง | Opcode | Byte | T-state | หมายเหตุ |
|---|---|---|---|---|
| NOP | 00 | 1 | 4 | ไม่ทำอะไรเลย · ตัวถ่วงมาตรฐาน |
| LD r, r′ | 40–7F | 1 | 4 | เช่น LD A,A · ระวังอย่าไปทับ B ที่ใช้นับ |
| INC r / DEC r | 04…3D | 1 | 4 | กระทบ flag |
| INC dd / DEC dd | 03/0B/… | 1 | 6 | ไม่กระทบ flag เลย |
| LD r, n | 06…3E | 2 | 7 | ตัวถ่วงยอดนิยมของอาจารย์ |
| ADD A, n | C6 | 2 | 7 | อาจารย์ใช้ใน Lab 3 |
| LD dd, nn | 01/11/21/31 | 3 | 10 | ตัวถ่วงก้อนใหญ่ · อาจารย์ใช้ใน Lab 3 |
| JP nn | C3 | 3 | 10 | 10 T เสมอ ไม่ว่ากระโดดหรือไม่ |
| PUSH qq / POP qq | C5/C1/… | 1 | 11 / 10 | ต้องมี SP ที่ถูกต้องก่อน |
| DJNZ e | 10 | 2 | 13 / 8 | 13 T เมื่อกระโดด · 8 T รอบสุดท้าย · M1 ยาว 5 T |
| JR e | 18 | 2 | 12 | — |
| JR cc, e | 20/28/30/38 | 2 | 12 / 7 | 12 เมื่อกระโดด · 7 เมื่อไม่กระโดด |
| HALT | 76 | 1 | 4 | ปิดท้ายโปรแกรม |
รอบที่ยังกระโดดกลับ ใช้ 13 T · รอบสุดท้ายที่ B เป็น 0 แล้วไม่กระโดด ใช้ 8 T
ถ้าคิดเป็น 13 T ทุกรอบ จะเกินไป 5 T เสมอ — คนพลาดตรงนี้กันเยอะที่สุด
โครงโปรแกรมมาตรฐานที่อาจารย์สอนใน Lab 3 หน้าตาแบบนี้
CPU "z80.tbl" ; directive — บอกว่าแปลเป็นภาษา Z80
HOF "INT8" ; directive — ให้ออกไฟล์ Intel HEX
ORG 2000H
LD B, n ; 7 T ← ตั้งจำนวนรอบ
LOOP: <ตัวถ่วงในลูป> ; รวม P T ← ทำซ้ำทุกรอบ
DJNZ LOOP ; 13 T / 8 T
<ตัวถ่วงท้าย> ; รวม Q T ← ทำครั้งเดียว
HALT ; 4 T
รวม = 7 ; LD B,n
+ (P + 13) × (n − 1) ; รอบที่กระโดดกลับ มี n−1 รอบ
+ (P + 8) ; รอบสุดท้าย ไม่กระโดด
+ Q ; ตัวถ่วงท้าย
+ 4 ; HALT
กระจายแล้วยุบ:
= (P + 13)·n + 7 − (P + 13) + (P + 8) + Q + 4
ใบงาน Lab 3 ให้โปรแกรมนี้มา แล้วบอกว่าต้องได้ T-state = 1234 พอดี
cpu "z80.tbl"
hof "INT8"
org 2010h
ld b, 30 ; 7 T
loop: ld de, 1122 ; 10 T ┐
ld de, 3344 ; 10 T ├ P = 27
ld a, 1 ; 7 T ┘
DJNZ loop ; 13 / 8 T
ld a, 1 ; 7 T ┐
ld a, 2 ; 7 T ├ Q = 28
ld a, 3 ; 7 T │
add a, 4 ; 7 T ┘
halt ; 4 T
N = (P + 13)·n + 6 + Q
= (27 + 13) × 30 + 6 + 28
= 40 × 30 + 34
= 1200 + 34
= 1234 ✓ ตรงกับที่ใบงานบอกเป๊ะ
T = 1 / f = 1 / (2 × 10⁶ Hz) = 0.5 × 10⁻⁶ s = 0.5 µs
N = เวลาที่ต้องการ / T
= 1 ms / 0.5 µs
= 1000 µs / 0.5 µs
= 2000 T-state
ลูป DJNZ เปล่า ๆ ที่ยาวที่สุด: n = 256 (ใส่ LD B,0)
Nmax = (0 + 13) × 256 + 6 + 0 = 3328 + 6 = 3334 T
3334 T > 2000 T ⇒ ลูปชั้นเดียวพอ ไม่ต้องซ้อนลูป ✓
ลองแบบง่ายที่สุดก่อน คือไม่ใส่ตัวถ่วงในลูปเลย (P = 0) และยังไม่มีตัวถ่วงท้าย (Q = 0)
2000 = (0 + 13)·n + 6 + 0
1994 = 13n
n = 1994 / 13 = 153.4... ← ไม่ลงตัว
13 × 100 = 1300 เหลือ 1994 − 1300 = 694
13 × 50 = 650 เหลือ 694 − 650 = 44
13 × 3 = 39 เหลือ 44 − 39 = 5
⇒ 153 เศษ 5
เหลือเศษ 5 T แต่ไม่มีคำสั่งไหนใช้ 5 T (ที่มีคือ 4, 6, 7, 10, 11, …) จึงต้องขยับ n ลงมา 1 แล้วเอาเศษที่มากขึ้นไปทำเป็นตัวถ่วงท้ายแทน
ลอง n = 152: 13 × 152 = 1976
Q = 2000 − 6 − 1976 = 18 T
18 T ประกอบได้จาก: NOP(4) + LD A,1(7) + LD A,2(7) = 18 ✓
CPU "z80.tbl" ; โหลดตารางคำสั่ง Z80
HOF "INT8" ; ออกไฟล์ Intel HEX 8 บิต
ORG 2000H
DELAY: LD B, 152 ; 7 T — ตั้งจำนวนรอบ (152 = 98H)
LOOP: DJNZ LOOP ; 13 T × 151 รอบ + 8 T รอบสุดท้าย
NOP ; 4 T ┐
LD A, 1 ; 7 T ├ ตัวถ่วงท้าย Q = 18 T
LD A, 2 ; 7 T ┘
HALT ; 4 T
END
| คำสั่ง | T-state ต่อครั้ง | ทำกี่ครั้ง | รวม |
|---|---|---|---|
| LD B, 152 | 7 | 1 | 7 |
| DJNZ LOOP (กระโดด) | 13 | 151 | 1,963 |
| DJNZ LOOP (รอบสุดท้าย) | 8 | 1 | 8 |
| NOP | 4 | 1 | 4 |
| LD A, 1 | 7 | 1 | 7 |
| LD A, 2 | 7 | 1 | 7 |
| HALT | 4 | 1 | 4 |
| รวมทั้งหมด | 2,000 T | ||
เวลาจริง = 2000 T × 0.5 µs = 1000 µs = 1 ms ✓ ตรงเป๊ะ
ถ้าอยากให้เลข n สวยกว่า ใส่ตัวถ่วง 1 ตัวในลูปให้ P = 7 (ลูปละ 20 T)
2000 = (7 + 13)·n + 6 + Q
1994 = 20n + Q
20 × 99 = 1980 ⇒ Q = 14 = LD A,2 (7) + LD A,3 (7) ✓
LD B, 99 ; 7 T
LOOP: LD A, 1 ; 7 T ← P = 7
DJNZ LOOP ; 13 / 8 T
LD A, 2 ; 7 T ┐ Q = 14 T
LD A, 3 ; 7 T ┘
HALT ; 4 T
ตรวจ: 20 × 99 + 6 + 14 = 1980 + 20 = 2000 T ✓
แบบนี้ตรงสไตล์ Lab 3 ของอาจารย์มากกว่า (มีตัวถ่วงทั้งในลูปและท้ายลูป) ตอบแบบไหนก็ได้ ขอแค่สมการกับตารางตรวจถูก
LD B, 154 แล้ว DJNZ เปล่า ๆ
N = 13 × 154 + 6 = 2002 + 6 = 2008 T = 1004 µs
คลาดเคลื่อน +4 µs คิดเป็น +0.4%
เขียนกำกับว่า “คลาดเคลื่อน +0.4% ถ้าต้องการแม่นยำกว่านี้ ให้ลด n เป็น 152 แล้วเติมตัวถ่วงท้าย 18 T” — แสดงว่าเข้าใจ ไม่ใช่เดา
ใส่ความถี่กับเวลาที่ต้องการ ระบบจะหาโปรแกรมที่ได้ T-state ตรงเป๊ะ ให้ พร้อมตารางตรวจ — ใช้ซ้อมทำโจทย์ตัวเลขอื่นได้
โปรแกรมที่ได้จะขึ้นต้นด้วย cpu และ hof เสมอ
ตามรูปแบบที่ Cross-16 ต้องการ — สองบรรทัดนี้เป็น directive ไม่ถูกนับ T-state
(มันคืออะไร มาจากไหน)
ลูป DJNZ ชั้นเดียวยาวสุด 3,334 T (เพราะ B เก็บได้แค่ 1–256) ที่ 2 MHz คือ 1.667 ms · ถ้าต้องการนานกว่านั้นต้องซ้อนลูป
LD B, nb ; 7 T ← ตัวนับชั้นนอก
OUTER: LD C, nc ; 7 T ← ตัวนับชั้นใน (ตั้งใหม่ทุกรอบนอก)
INNER: DEC C ; 4 T
JP NZ, INNER ; 10 T ← JP ใช้ 10 T เสมอ ทั้งกระโดดและไม่กระโดด
DJNZ OUTER ; 13 / 8 T
<ตัวถ่วงท้าย Q>
HALT ; 4 T
ลูปในทำงาน nc รอบ รอบละ (4 + 10) = 14 T
⇒ ลูปในกินเวลา 14·nc ทุกครั้งที่รอบนอกวน
ค่าใช้จ่ายของรอบนอก 1 รอบ (แบบกระโดด) = 7 + 14·nc + 13 = 20 + 14·nc
N = (20 + 14·nc)·nb + 6 + Q
รูปแบบเหมือนสมการเดิมเป๊ะ แค่เปลี่ยน (P + 13) เป็น (20 + 14·nc)
N = 100,000 µs / 0.5 µs = 200,000 T
เลือก nc = 200 ⇒ ค่าใช้จ่ายรอบนอก = 20 + 14(200) = 2,820 T
200,000 − 6 = 199,994
199,994 / 2,820 = 70 เศษ 2,594 ← Q ใหญ่เกินไป
เลือก nc = 142 ⇒ 20 + 14(142) = 2,008 T
199,994 / 2,008 = 99 เศษ 1,202 ← ยังใหญ่
ปรับให้ลงตัว: nb = 100, nc = 142 ⇒ 2,008 × 100 = 200,800 (เกิน)
nb = 99, nc = 143 ⇒ (20 + 2,002) × 99 = 200,178 (เกิน 178)
nb = 99, nc = 142 ⇒ 2,008 × 99 = 198,792
Q = 200,000 − 6 − 198,792 = 1,202 T ← ยังต้องถ่วงอีกเยอะ
ตัวเลขเริ่มยุ่ง — ในห้องสอบให้ใช้เครื่องมือด้านบนซ้อมไว้ก่อน แล้วในข้อสอบเลือกคู่ (nb, nc) ที่หารลงตัวสวย ๆ หรือยอมรับความคลาดเคลื่อนแล้วเขียนกำกับไว้
จงเขียนโปรแกรมหน่วงเวลา 1 ms ที่ความถี่ 4 MHz พร้อมแสดงวิธีคำนวณ
T = 1/4 MHz = 0.25 µs
N = 1000 µs / 0.25 µs = 4000 T
ลูปเดียวยาวสุด 3334 T < 4000 T ⇒ ต้องใส่ตัวถ่วงในลูป
ลอง P = 7 (ลูปละ 20 T):
4000 = 20n + 6 + Q
3994 = 20n + Q
20 × 199 = 3980 ⇒ Q = 14 = LD A,1 + LD A,2 ✓
แต่ n = 199 ≤ 256 ✓ ใช้ได้
LD B, 199 ; 7 T
LOOP: LD A, 1 ; 7 T
DJNZ LOOP ; 13 / 8 T
LD A, 2 ; 7 T
LD A, 3 ; 7 T
HALT ; 4 T
ตรวจ: 20 × 199 + 6 + 14 = 3980 + 20 = 4000 T = 1000 µs ✓
บทเรียน: ความถี่สูงขึ้น 2 เท่า ⇒ ต้องใช้ T มากขึ้น 2 เท่าเพื่อเวลาเท่าเดิม
จงเขียนโปรแกรมหน่วงเวลา 100 µs ที่ความถี่ 2 MHz
N = 100 µs / 0.5 µs = 200 T
200 = 13n + 6 + Q
194 = 13n + Q
13 × 14 = 182 ⇒ Q = 12 = NOP + LD A,1 (4 + 7 = 11) ✗
12 = INC HL(6) + INC HL(6) ✓ หรือ NOP×3 = 12 ✓
LD B, 14 ; 7 T
LOOP: DJNZ LOOP ; 13 × 13 + 8 = 177 T
NOP ; 4 T ┐
NOP ; 4 T ├ Q = 12 T
NOP ; 4 T ┘
HALT ; 4 T
ตรวจ: 7 + 177 + 12 + 4 = 200 T = 100 µs ✓
สั้น ๆ แบบนี้ตัวถ่วงท้ายมีสัดส่วนเยอะ — ตรวจให้ดีอย่าลืมนับ HALT
โปรแกรมต่อไปนี้หน่วงเวลาได้เท่าไร ถ้า clock = 2 MHz
LD B, 50
L1: LD DE, 0
NOP
DJNZ L1
HALTตัวถ่วงในลูป P = LD DE,0 (10 T) + NOP (4 T) = 14 T
ตัวถ่วงท้าย Q = 0
N = (P + 13)·n + 6 + Q
= (14 + 13) × 50 + 6 + 0
= 27 × 50 + 6
= 1350 + 6
= 1356 T
เวลา = 1356 × 0.5 µs = 678 µs
ถ้าโจทย์ถามกลับด้านแบบนี้ ให้แยก P กับ Q ออกมาก่อนแล้วแทนสมการ — ไม่ต้องนั่งไล่ทีละรอบ
จงเขียนโปรแกรมกะพริบ LED ที่พอร์ต 40H โดยให้ติด 1 ms ดับ 1 ms ที่ความถี่ 2 MHz ด้วยการเรียก subroutine หน่วงเวลา
CPU "z80.tbl"
HOF "INT8"
ORG 0000H
LD SP, 1FFFH ; ต้องตั้ง SP ก่อนใช้ CALL
MAIN: LD A, 0FFH
OUT (40H), A ; LED ติดหมด
CALL DLY1MS
XOR A
OUT (40H), A ; LED ดับหมด
CALL DLY1MS
JR MAIN
DLY1MS: PUSH AF ; รักษาค่า A และ B ของผู้เรียก
PUSH BC
LD B, 152
DL1: DJNZ DL1
NOP
LD A, 1
LD A, 2
POP BC
POP AF
RET
END
เวลาจริงจะไม่ใช่ 1 ms พอดีแล้ว เพราะเพิ่ม
CALL (17 T), RET (10 T), PUSH×2 (22 T), POP×2 (20 T)
และไม่มี HALT (−4 T)
ถ้าโจทย์ต้องการ 1 ms ที่ปลายสาย ต้องเอา 65 T ส่วนเกินนี้ไปหักออกจากตัวถ่วงด้วย —
เขียนบอกไว้ในคำตอบจะได้คะแนนความละเอียด
จงออกแบบโปรแกรมที่ให้ T-state รวมเท่ากับ 2569 พอดี (สมมติเป็นรหัสนักศึกษา 4 ตัวท้าย) โดยใช้โครงเดียวกับ Lab 3
N = (P + 13)·n + 6 + Q = 2569
⇒ (P + 13)·n + Q = 2563
ลองให้ลูปหนัก ๆ เหมือน Lab 3: P = 27 (LD DE,nn ×2 + LD A,n) ⇒ P + 13 = 40
2563 / 40 = 64 เศษ 3 ← Q = 3 ทำไม่ได้ (ไม่มีคำสั่ง 3 T)
n = 63 ⇒ 40 × 63 = 2520 ⇒ Q = 43 = 10+10+7+7+...
43 = 10 + 10 + 10 + 7 + 6 ✓
CPU "z80.tbl"
HOF "INT8"
ORG 2010H
LD B, 63 ; 7 T
LOOP: LD DE, 1122 ; 10 T ┐
LD DE, 3344 ; 10 T ├ P = 27
LD A, 1 ; 7 T ┘
DJNZ LOOP ; 13 / 8 T
LD DE, 1 ; 10 T ┐
LD DE, 2 ; 10 T │
LD DE, 3 ; 10 T ├ Q = 43
LD A, 4 ; 7 T │
INC HL ; 6 T ┘
HALT ; 4 T
ตรวจ: 40 × 63 + 6 + 43 = 2520 + 49 = 2569 T ✓
วิธีนี้คือสิ่งที่ใบงาน Lab 3 ให้ทำจริง ๆ (เปลี่ยน 1234 เป็นรหัสของตัวเอง) — ถ้าข้อสอบออกแนวนี้ ให้ใช้สมการเดียวกันเลย
LD B,n กับ HALT
LD B,n ไว้ในลูป
LD B,0 หรือ POP BCLD B,0 = วน 256 รอบ