[ บทความ : เรียนรู้ z80 ] ตอนที่ 12 เรื่อง คำสั่งกระโดด

เรียนรู้ Z80 ตอนที่ 12

คำสั่งกระโดด

กลับมาแล้วครับ บทความ Z80 ตอนที่ 12 ... ในบทความคราวนี้เราจะมาศึกษาการทำงานคำสั่งที่เกี่ยวกับการกระโดด ซึ่ง เป็นกลุ่มคำสั่งที่ มีความสำคัญมาก เนื่องจาก เรามักเขียนโปรแกรม โดยอาศัยหลักของเงื่อนไข ถ้าโปรแกรมของเราไม่ มีการกำหนดเงื่อนไข โปรแกรมนั้นก็จะทำงานโดยไม่ได้สนใจสิ่งรอบข้างเลย .. เช่น เราต้องการเขียนโปรแกรมควบคุมหุ่นยนต์ แต่เรากำหนดว่า ให้เดินหน้า โดยการหมุนมอเตอร์ไป 10 รอบ แล้วเลี้ยวซ้าย พรอ้มทั้งเดินหน้าไปอีก 5 รอบ แล้วเลี้ยวขวา หลังจากนั้นเดินหน้าอีก 10 รอบ แต่เวลาที่หุ่นยนต์ทำงานจริงๆ การหมุน 10 รอบนั้น อาจจะไม่ได้ระยะทางที่เรากำหนดเอาไว้ และถ้าระหว่างทางที่หมุนมอเตอร์ไปได้ 3 รอบ แล้วหุ่นของเราไปชนกับอะไรก็ไม่รู้ แล้วอย่างนี้ พอมันทำงานต่อไป ... หุ่นยนต์ของเรา ก็ไม่สามารถเดินทางไปยังจุดที่เรากำหนดได้ ... พอมองภาพออกไหมครับ ว่า ถ้าเรามีการกำหนดเงื่อนไข ในการตรวจสอบปัญหา ให่มากขึ้น การทำงานของโปรแกรม (หุ่นยนต์) ก็จะทำงานได้ถูกต้องและสมบูรณ์มากขึ้นด้วย (ยากเนอะ ... ตัวอย่างอะไรเนี่ย ... น่าจะหาอะไรที่ง่ายๆ กว่านี้ ใช่ไหม ... :D) ... ชีวิตมันก็ขีดเส้นล่วงหน้าไม่ได้อย่างนี้แหละครับ (ฮ่าๆ) ... ว่าแล้วเรามาดูรูปแบบของคำสั่งกันเลยดีกว่าครับ

Instruction
Source/Target
Flag
Operation
JP
(HL)
ไม่มีผลกับ flag
PC <- HL
JP
(IX)
ไม่มีผลกับ flag
PC <- IX
JP
(IY)
ไม่มีผลกับ flag
PC <- IY
JP
nn
ไม่มีผลกับ flag
PC <- nn
JP
cc, nn
ไม่มีผลกับ flag
if cc then PC <- nn
JR
d
ไม่มีผลกับ flag
PC <- PC+d
JR
cc, d
ไม่มีผลกับ flag
if cc = 0 then PC <- PC+d
DJNZ
d
ไม่มีผลกับ flag
B <- B-1
if B != 0 then PC <- PC+d
ตาราง 12-1 ชุดคำสั่งสำหรับการกระโดดแบบต่างๆ

หมายเหตุ

nn คือ ตัวเลขขนาด 16 บิต
cc คือ สถานะของ Flag อันได้แก่ NZ (Z=0), Z (Z=1), NC (C=0), C (C=1), PO (P=0), PE (P=1), P (S=0) และ M (S=1)
d คือ ตัวเลขขนาด 8 บิต มีค่าตั้งแต่ -126 ถึง 129

ตัวอย่างแรก เป็นตัวอย่างการคูณครับ ... ผมกำหนดให้ตัวตั้ง เก็บใน Register C ตัวคูณ เก็บใน Register C แล้วกำหนดให้ผลลัพธ์เก็บใน Register L ... หลักการคูณที่ผมใช้นี้เป็นการคูณแบบเด็กๆ คือว่า ผมจะเอา 3 คูณกับ 5 ซึ่งวิธีการคิดแบบเด็กๆ ก็คือ การนำ 3 มาบวกกัน 5 ครั้ง ... จากตรงนี้ได้ว่า คำสั่งที่เรา จะใช้น่าจะเป็น ADD (ก็เราจะบวกไง) ... แล้วเราก็จะลดค่าจำนวนครั้งที่ทำลงทีละ 1 ซึ่งก็จะใช้คำสั่ง SUB แต่ว่า .. ทั้ง 2 คำสั่งนี้ใช้ Register A ในการเป้นตัวตั้ง และตัวเก็บผลลัพธ์น่ะสิ ... ดังนั้น เราจึงต้องเสียเวลาสำหรับ การถ่ายโอนค่าไปมาระหว่าง Register L (เก็บผลลัพธ์) , Register B (เก็บจำนวนครั้ง) และ Register A (ใช้ในการบวกและลบ) ... ส่วนออกจากการวนรอบการบวก ผมใช้วิธีการลดค่า Register B ทีละ 1 โดย ผมเริ่มจาก นำค่าจาก B มาเก็บใน A แล้วลดค่า A ลง 1 ค่า ถ้าผลลัพธ์เป็น 0 นั่นคือ Zero-flag เป็น 1 ผมก็จะให้มันกระโดดออกมาจาก วงรอบการบวก ... คำสั่งที่ใช้คือ

JP Z, mul1e

ซึ่งผลลัพธ์ของโปรแกรมที่ผมทดลองเขียน จะมีหน้าตาดังนี้ครับ


	;
	; Filename : Mul1.asz
	; Author   : Supachai Budsaratij   (raek@se-ed.net)
	; Date     : 04/01/2001
	; Hardware : ET-Board V6 (Z80 Mode)
	;            ET-Board V5
	;            ET-Board V4
	;            ET-Board V3.5 NewPower
	;            CP-jr180 Plus
	; Note     : L = C x B , C=3, B=5, and return the result to L.
	;
	;
	        INCL    "etv6.inz"         ; Include header for ET-V6
	;        INCL    "etv4.inz"         ; Include header for ET-V4
	;        INCL    "etv35.inz"        ; Include header for ET-V3.5
	;        INCL    "jr180.inz"        ; Include header for CP-jr180 Plus

	        ORG     UMEM_ORG        ; Start at UMEM_ORG

	main
	        LD      C,3             ; C = 3
	        LD      B,5             ; B = 5
	        LD      A,0             ; A = 0
	mul1:
	        ADD     A,C             ; A = A+C
	        LD      L,A             ; L = A
	        LD      A,B             ; A = B
	        SUB     1               ; A = A - 1
	; If Zero-flag = 1 then jump to mul1e
	        JP      Z, mul1e
	; else
	        LD      B,A             ; B = A
	        LD      A,L             ; A = L
	        JP      mul1            ; Do agian.
	mul1e:

	        HALT

	        END

อ่านยากไหมครับ ... อืม มาลองดู Flowchart ของมันกันดีกว่า ...

ครั้งนี้ผมขอไม่แสดง .LST กับ .HEX นะครับ เพื่อเป็นการประหยัดเวลาของผู้อ่านบทความ (ไม่ต้องเสียเวลา download นาน) .. และเนื่องจาก ผมเองก็ได้อธิบายการ Debug โปรแกรมมาหลายตอนแล้ว ดังนั้น ตั้งแต่นี้ไป ผมจะขอไม่แสดงการ Debug อีกแล้ว นะครับ ใครที่สงสัยว่า ผลการทำงานเป็นอย่างไร ก็ขอให้ทดสอบตามวิธีที่ผ่านๆ มาได้เลยครับ ...

ตัวอย่างที่ 2 ก็จะเป็นตัวอย่างโปรแกรมการหาร ... หลักการผมก็ใช้วิธีแบบเด็กๆ เขาใช้กันเหมือนเดิมครับ นั่น คือ ทำการลดค่าตัวตั้ง ครั้งละเท่าที่ตัวหารกำหนด ในตัวอย่าง ผมให้ตัวตั้งมีค่าเป็น 20 (เก็บใน Register C) และตัวหารเป็น 3 (เก็บในรีจิสเตอร์ H) ... และทุกครั้งที่ผมลบค่าออกจากตัวตั้ง แล้วผลลัพธ์ที่ได้จากการลบ มากกว่า 0 ผมก็จะทำการเพิ่มค่าตัวนับ จำนวนครั้งการทำงานของโปรแกรมขึ้นอีก 1 (ผมใช้ Register L) ... อ๊ะ ... ผมบอกว่า จะลบแล้วรอดูผลว่า ... ได้ค่าเป็นลบหรือไม่ .... จากตรงนี้ ... ผมเลยเลือก

JP M, div1e
จากบรรทัดโปรแกรมด้านบน มันหมายความว่า ถ้าผลลัพธ์ของ Sign-flag เป็น 1 (ค่าจากการทำงานหลังสุดได้ค่า ที่ต่ำกว่า 0) เราก็จะให้โปรแกรมหยุดทำงาน ... ดังนั้น โปรแกรมเลยมีหน้าตาอย่างนี้ครับ


	;
	; Filename : Div1.asz
	; Author   : Supachai Budsaratij   (raek@se-ed.net)
	; Date     : 04/01/2001
	; Hardware : ET-Board V6 (Z80 Mode)
	;            ET-Board V5
	;            ET-Board V4
	;            ET-Board V3.5 NewPower
	;            CP-jr180 Plus
	; Note     : L = C / H , C=20, H=3, and return the result to L.
	;
	;
	        INCL    "etv6.inz"         ; Include header for ET-V6
	;        INCL    "etv4.inz"         ; Include header for ET-V4
	;        INCL    "etv35.inz"        ; Include header for ET-V3.5
	;        INCL    "jr180.inz"        ; Include header for CP-jr180 Plus

	        ORG     UMEM_ORG        ; Start at UMEM_ORG

	main
	        LD      C,20            ; C = 20
	        LD      H,3             ; H = 3
	        LD      L,0             ; L = 0
	div1:
	        LD      A,C             ; A = C
	        SUB     H               ; A = A-H
	; If Sign-flag = 1 then jump to div1e
	        JP      M, div1e
	; else
	        LD      C,A             ; C = A
	        LD      A,L             ; A = L
	        ADD     A,1             ; A = A+1
	        LD      L,A             ; L = A
	        JP      div1            ; Do agian.
	div1e:

	        HALT

	        END

มาดู Flowchart ของโปรแกรมกันดีกว่าครับ

เป็นอย่างไรบ้างครับ สำหรับโปรแกรมการคูณและการหาร แนวคิดของมันไม่ยากใช่ไหมครับ ... แต่การทำงานของมัน ในแง่ของประสิทธิภาพนั้น ไม่ดีเอาเสียเลย ... เพราะมีการวนรอบหลายครั้งมาก ยิ่ง เราลองนำ 255 ตั้ง แล้วหารด้วย 1 จะเห็นว่า ต้องวนรอบตั้ง 255 ครั้งแน่ะ ... ยิ่งหารด้วย 0 ผลลัพธ์ยิ่งแย่เข้าไปใหญ่ เพราะมันจะนำ 0 ไปลบ ... ลบเท่าไรก็ลบไม่หมด (จริงไหม) โปรแกรมก็จะ Hang (หมายถึงทำเรื่อยๆ ไม่รู้จะจบเมื่อไร) ... ดังนั้น ผมอยากให้ผู้อ่านได้ลองปรับปรุงประสิทธิภาพของโปรแกรม กันดูครับ ... (ถ้าสังเกตสักนิด จะเห็นว่า ผมเขียน Flowchart และออกแบบโปรแกรม ไม่ดีเอาเสียเลย ไม่มีระบบ ดูไม่ออกเลยว่าเป็น การทำงานแบบ if หรือว่า Loop กันแน่ ....) ส่วนคราวหน้า ผมจะขึ้นคำสั่งเกี่ยวกับ I/O แล้วก็ตามด้วย คำสั่งเกี่ยวกับการกระทำทาง Logic (พวก And, OR, ...) และคำสั่งที่ตกๆ หล่นๆ ไปหลายตัว คาดว่า ถ้าเป็นไปได้ อีกสัก 5 ครั้ง บทความเกี่ยวกับ Z80 น่าจะลงโดยดีครับ ...

ส่วนก่อนจบบทความนี้ ผมขอขอบพระคุณ อ.วีระชัย คอนจอหอ (โปรแกรมวิชาคอมพิวเตอร์ศึกษา คณะคอมพิวเตอร์และเทคโนโลยีชั้นสูง สถาบันราชภัฏเพชรบุรี) เป็นอย่างสูงเลยครับ เพราะอาจารย์ท่านเคี่ยวเข็ญ ให้ผม เขียน Flowchart มาอย่างหนัก ... ผมเลยสามารถออกแบบโปรแกรมได้มากมาย โดยไม่ต้องอาศัยการเขียนโปรแกรมแบบลอง ผิด ลองถูก ... ทั้งนี้เพราะว่า Flowchart จะช่วยให้เราลำดับความคิด และทดสอบการทำงานของมันได้ก่อนเขียนโปรแกรมจริง ... แต่ข้อเสียของ Flowchart คงเป็นตรงที่ ต้องเสียเวลาออกแบบ แล้วค่อยเขียนโปรแกรม มันน่าเบื่อสำหรับ เด็กวัยรุ่น ใจร้อนครับ ... ถึงอย่างไรก็แล้วแต่ ถ้าอยากเป็นนักเขียนโปรแกรมที่ดี หรือนักออกแบบระบบที่ดี การเขียน Flowchart เป็นทางออกที่ดีมากๆ ครับ ...



เขียนโดย : ศุภชัย บุศราทิจ
Author : Supachai Budsaratij
e-mail : raek@se-ed.net
วันที่ทำการปรับปรุง : ๑ เม.ย. ๒๕๔๔