[ บทความ : เรียนรู้ z80 ] ตอนที่ 7 เรื่อง คำสั่งที่เกี่ยวกับ stack |
เรียนรู้ Z80 ตอนที่ 7
คำสั่งที่เกี่ยวกับ stack
stack เป็นการจัดการกับหน่วยความจำรูปแบบหนึ่ง ซึ่งอาศัยหลักการทำงานที่เรียก LIFO (Last In First Out) หรือ เรียกอีกอย่างหนึ่งได้ว่า ข้อมูลที่เราใส่เข้าไปทีหลัง จะถูกนำออกมาก่อนข้อมูลที่เราใส่ไปก่อนหน้าข้อมูลชุดนั้นๆ
การกระทำที่เราใช้กับหน่วยความจำ stack นั้นมี 2 อย่าง คือ การ Push และ Pop
Push เป็นความหมายแทนการนำข้อมูลไปเก็บเอาไว้ในหน่วยความจำ stack
Pop เป็นความหมายแทนการนำข้อมูลออกมาจากหน่วยความจำ stackนั่นหมายความว่า แต่ละครั้งที่เราสั่ง push ก็จะมีการนำข้อมูลไปซ้อนเอาไว้ด้านบนของข้อมูลที่เราได้ push ไปก่อนหน้านี้ เช่น เราได้สั่งเรียงลำดับดังต่อไปนี้
ในหน่วยความจำของ stack ก็จะเป็นดังนี้ครับ
push 5 push 10 push 2 push 14
ลำดับ ข้อมูล 4 14 3 2 2 10 1 5 ดังนั้น ในการสั่ง pop ก็จะได้ข้อมูลเรียงออกมาเป็นดังนี้คือ 14,2,10 และ 5 ตรงนี้จะเห็นว่า ข้อมูลตัวแรกที่เราใส่ใน stack ซึ่งก็คือ 5 จะออกมาเป็นตัวสุดท้าย แต่ข้อมูลที่เรา push เป็นลำดับสุดท้าย กลับออกมาเป็นตัวแรก ... นี่ล่ะครับ เขาถึงเรียกว่า LIFO หรือ Last In First Out (เข้าทีหลังออกก่อน)
ใน Z80 นั้น มีการทำงานแบบ stack เหมือนกันครับ ซึ่งการทำงานของ stack นั้น จะทำงานแบบ 16 บิต นั่นหมายความว่า การ push และ pop จะเป็นการทำงานกับข้อมูลขนาด 16 บิต นอกจากนี้ Z80 ก็จะมี รีจิสเตอร์ ที่ชื่อ SP เป็นตัวเก็บค่าตำแหน่งของหน่วยความจำ ที่เอาไว้ใส่ (push) ข้อมูลชุดถัดไป ... ลักษณะการทำงานของ stack ของ z80 นั้นเป็นแบบการลดค่า ซึ่งหมายความว่า เริ่มต้นนั้น SP จะเก็บค่าของตำแหน่งเริ่มต้นของ stack สมมติว่าเป็น 7FFFh หลังจากนั้น พอเราสั่ง push ค่าของ SP ก็จะถูกลดลง จาก 7FFFh มาเป็น 7FFDh และถ้าเรา push ข้อมุลลงไปอีก ค่าของ SP ก็จะเหลือเป็น 7FFBh ... ลักษณะอย่างนี้เป็น stack แบบ ลดค่า ... ดังนั้น เวลาที่เรา pop ค่าของ SP ก็จะเพิ่มขึ้นมา เช่น เราสั่ง POP 1 ครั้ง โดย ก่อนหน้านี้ SP มีค่าเป็น 7FFBh หลังจาก คำสั่ง POP ทำงานเสร็จแล้ว ค่าของ SP ก็จะเปลี่ยนมาเป็น 7FFDh ... แล้วถ้าเรา POP อีกครั้ง ค่าของ SP ก็จะเป็น 7FFFh ตามลำดับ หมายเหตุ ที่เราต้องระวังก็คือว่า ถ้าเรา POP ไปเรื่อยๆ ค่าของ SP ก็จะเพิ่มไปเรื่อยๆ อาจจะทำให้ ........... (ลองเติมเอาเองนะครับ) และ ในทางกลับกัน ถ้าเราสั่ง PUSH ไปเรื่อยๆ ค่าของ SP ก็จะลดลงเรื่อยๆ อาจจะทำให้ ........... (ลองเติมเองนะครับ)
ทั้งหมดที่ผมกล่าวมานั้น เป็นหลักการทำงานของ stack ครับ ... คราวนี้เรามาลองดูคำสั่งที่เกี่ยวข้องกับ stack กันดีกว่าครับ ... ใน z80 มีคำสั่งที่เกี่ยวกับ stack ดังนี้ครับ
ตาราง 6-1 ชุดคำสั่งสำหรับ Stack
Instruction Source/Target Flag Operation PUSH - (SP-2) <--LO(qq), (SP-1)<--HI(qq) ; SP=SP-2 PUSH IX - (SP-2) <--LO(IX), (SP-1) <--HI(IX) ; SP=SP-2 PUSH IY - (SP-2) <--LO(IY) , (SP-1) <-- HI(IY) ; SP = SP-2 POP - HI(qq) <-- (SP+1) , LO(qq) <-- (SP) ; SP = SP+2 POP IX - HI(IX) <-- (SP+1), LO(IX) <-- (SP) ; SP = SP+2 POP IY - HI(IY) <-- (SP+1) , LO(IY) <-- (SP) ; SP = SP+2 หมายเหตุ qq เป็นรีจิสเตอร์คู่ 16 บิต อันได้แก่ AF, BC, DE และ HL
ตัวอย่างโปรแกรม
; ; Filename : TStack.asz ; Author : Supachai Budsaratij (raek@se-ed.net) ; Date : Dec 23, 2000 ; Hardware : ET-Board V6 (Z80 Mode) ; INCL "etv6.inz" ; Inlude header for ET-V6 ORG UMEM_ORG ; Start at UMEM_ORG main ; --- My code here LD HL,1C2Ah PUSH HL LD DE,2A00h PUSH DE LD HL,0000h POP HL POP DE ; ---End of program HALT ENDเมื่อคีย์โปรแกรมเสร็จแล้วให้ save ชื่อไฟล์เป็น tstack.asz หลังจากนั้น ก็สั่งคอมไพล์ดังนี้เลยครับ
az80 tstack.asz -l tstack.lst -o tstack.hexไฟล์ .LST ที่ได้จากการคอมไพล์
; ; Filename : TStack.asz ; Author : Supachai Budsaratij (raek@se-ed.net) ; Date : Dec 23, 2000 ; Hardware : ET-Board V6 (Z80 Mode) ; ; INCL "etv6.inz" ; Inlude header for ET-V6 ; ; filename : etv6.inz ; assembler : az80 ; author : Supachai Budsaratij (raek@se-ed.net) ; hardware : et-board 6 (Z-80 mode) ; date : October 12,2000 ; ; --- MEMORY ; 8000 UMEM_ORG EQU 08000h ; User RAM start bdff UMEM_END EQU 0BDFFh ; User RAM end c000 XMEM_ORG EQU 0C000h ; Expand RAM end dfff XMEM_END EQU 0DFFFh ; Expand RAM end ; --- I/O ; 0000 S8255_PA EQU 00h ; System 8255 port A 0001 S8255_PB EQU 01h ; System 8255 port B 0002 S8255_PC EQU 02h ; System 8255 port C 0003 S8255_CT EQU 03h ; System 8255 control port ; 0020 U8255_PA EQU 20h ; User 8255 port A 0021 U8255_PB EQU 21h ; User 8255 port B 0022 U8255_PC EQU 22h ; User 8255 port C 0023 U8255_CT EQU 23h ; User 8255 control port ; 004d SCN_INP EQU 4Dh ; SCN2681- Input port (Switch) 004e SCN_SEO EQU 4Eh ; SCN2681- Set output port (LED) 004f SCN_REO EQU 4Fh ; SCN2681- Reset output port(LED) ; 0060 CLCD_WC EQU 60h ; Character LCD write command 0061 CLCD_RC EQU 61h ; Character LCD read command 0062 CLCD_WD EQU 62h ; Character LCD write data 0063 CLCD_RD EQU 63h ; Character LCD read data ; 0064 GLCD_WC1 EQU 64h ; Graphics LCD write command (P1) 0065 GLCD_RC1 EQU 65h ; Graphics LCD read command (P1) 0066 GLCD_WD1 EQU 66h ; Graphics LCD write data (P1) 0067 GLCD_RD1 EQU 67h ; Graphics LCD read data (P1) ; 0068 GLCD_WC2 EQU 68h ; Graphics LCD write command (P2) 0069 GLCD_RC2 EQU 69h ; Graphics LCD read command (P2) 006a GLCD_WD2 EQU 6Ah ; Graphics LCD write data (P2) 006b GLCD_RD2 EQU 6Bh ; Graphics LCD read data (P2) ; 000a DD EQU 0AH 0020 EE EQU 20H a000 NN EQU 0A000H 000a N EQU 0AH 8000 ORG UMEM_ORG ; Start at UMEM_ORG 8000 main ; --- My code here 8000 21 2a 1c LD HL,1C2Ah 8003 e5 PUSH HL 8004 11 00 2a LD DE,2A00h 8007 d5 PUSH DE 8008 21 00 00 LD HL,0000h 800b e1 POP HL 800c d1 POP DE ; ---End of program 800d 76 HALT 800e END 0061 CLCD_RC 0063 CLCD_RD 0060 CLCD_WC 0062 CLCD_WD 000a DD 0020 EE 0065 GLCD_RC1 0069 GLCD_RC2 0067 GLCD_RD1 006b GLCD_RD2 0064 GLCD_WC1 0068 GLCD_WC2 0066 GLCD_WD1 006a GLCD_WD2 000a N a000 NN 0003 S8255_CT 0000 S8255_PA 0001 S8255_PB 0002 S8255_PC 004d SCN_INP 004f SCN_REO 004e SCN_SEO 0023 U8255_CT 0020 U8255_PA 0021 U8255_PB 0022 U8255_PC bdff UMEM_END 8000 UMEM_ORG dfff XMEM_END c000 XMEM_ORG 8000 mainส่วนไฟล์ HEX ก็จะมีหน้าตาของข้อมูลในไฟล์ดังนี้
:0E800000212A1CE511002AD5210000E1D176CD :00800E0171ทดสอบโปรแกรม
หลังจากได้ไฟล์ฐานสิบหกมาแล้ว ขั้นตอนต่อไปของเราก็คือ ส่งไฟล์ ฐานสิบหกนี้ไปเก็บไว้ที่ตัวบอร์ด ดังได้เคยอธิบายไปแล้ว ... เรามาดูกันตามลำดับเลยครับ
รูปที่ 6-1 โหลดโปรแกรมไปที่บอร์ด
หลังจากที่เราโหลดเสร็จแล้ว คราวนี้ก็เริ่มทำการ trace ทีละ step กันเลย โดยสั่งว่า t 8000 แล้วผลลัพธ์จากออกมาดังรูป 6-2 คำสั่งแรกที่เราสั่งให้ z80 ทำก็คือ LD HL, 1C2Ah ซึ่งเป็นการนำค่า 1C2Ah ไปเก็บในรีจิสเตอร์ HL โดยในการเก็บนั้นจะแบ่งข้อมูลเป็น 2 ไบต์ ส่วนหนึ่งเก็บเอาไว้ที่ รีจิสเตอร์ H (จากรูปจะเห็นว่า H เก็บ 1C) อีกตัวหนึ่งเก็บเอาไว้ใน รีจิสเตอร์ L( เก็บ 2A)
รูปที่ 6-2 ผลจากการสั่ง trace ครั้งที่ 1
คำสั่งต่อมาเป็นคำสั่ง PUSH HL ซึ่งผลลัพธ์ของการทำงานก็คือ ที่ (SP) เก็บค่า 1C2A (เพราะ HL เก็บค่า 1C2A) และค่าของ SP ถูกลดจากเดิม (ดูรูป 6-2 เทียบกับ 6-3) คือ จาก BF20 มาเป็น BF1E
รูปที่ 6-3 ผลจากการสั่ง trace ครั้งที่ 2
ในการ trace ครั้งที่ 3 จะได้ว่า คำสั่งที่เจอคือ LD DE,2A00h จึงทำให้รีจิสเตอร์ DE เก็บค่า 2A00h เอาไว้ ดังรูป 6-4
รูปที่ 6-4 ผลจากการสั่ง trace ครั้งที่ 3
หลังจากเราเอาค่าใน 2A00h เก็บใน DE แล้ว คราวนี้คำสั่งที่เราสั่งเป็นลำดับต่อมาก็คือ PUSH DE ทำให้ค่าของ SP ลดลงไปอีก 2 ไบต์ (เปรียบเทียบได้จากรูป 6-4 กับ 6-5) แต่คราวนี้ ข้อมูลที่ถูกชี้โดย SP หรือที่เห็นในรูป 6-5 เป็น (SP) นั้นก็จะเป็น 2A00 ซึ่งก็คือค่าที่เราเพิ่งจะ PUSH เข้าไปนั่นเอง
รูปที่ 6-5 ผลจากการสั่ง trace ครั้งที่ 4
การ trace ครั้งต่อมา เป็นการทำให้ค่าของ HL เป็น 0 ดังรูป 6-6
รูปที่ 6-6 ผลจากการสั่ง trace ครั้งที่ 5
เมื่อเรากำหนดให้ HL เป็น 0 ไปแล้ว จากคำสั่งก่อนหน้านี้ คราวนี้ก็ลอง POP HL ซึ่งหมายความว่า เอาค่าจากส่วนบนสุดของ stack มาเก็บในรีจิสเตอร์ HL ในรูป 6-7 จะเห็นว่า SP ถูกเพิ่มค่าจากเดิม ในรูป 6-6 เป็น BF1C มาเป็น BF1E เหมือนในรูป 6-3 และค่าของ HL ก็เป็น 2A00 เหมือน DE ทั้งนี้เพราะ เราเอาค่าของ DE ใส่ลงไปใน stack จากคำสั่งก่อนหน้านี้ (เพราะอย่างนี้ stack จึงถูกเรียกว่า LIFO ไงครับ)
รูปที่ 6-7 ผลจากการสั่ง trace ครั้งที่ 6
คราวนี้มาลอง POP DE ดูบ้าง ซึ่งผลลัพธ์ (ดูรูป 6-8) ก็คือ ค่าที่เก็บอยู่ใน stack ซึ่งก็คือ 1C2A ก็จะถูกนำมาเก็บใน DE นอกจากนี้ SP ก็จะถูกเพิ่มค่าอีก 2 ไบต์ จาก BF1E มาเป็น BF20 เหมือนตอนแรกที่โปรแกรมทำงาน (ดูได้จากรูป 6-2) สรุปได้ว่า จากโปรแกรมที่เราเขียนไปทั้งหมด ก็มีความหมายว่า เราทำการสลับค่าที่เก็บใน HL กับ DE นี่เป็นประโยชน์อย่างหนึ่งของ stack ที่ทำให้เราสามารถสลับค่าระหว่าง รีจิสเตอร์ได้ง่ายๆ
รูปที่ 6-8 ผลจากการสั่ง trace ครั้งที่ 7
ส่วนคำสั่งสุดท้าย ก็คือ HALT หรือให้ z80 หยุดทำงาน
รูปที่ 6-9 ผลจากการสั่ง trace ครั้งที่ 8
ตอนนี้เราศึกษาชุดคำสั่งไปแล้วด้วยกัน 2 ชนิดนะครับ คือ LD (คลิกที่นี่เพื่ออ่านบทความเก่า) กับคำสั่ง PUSH/POP ซึ่งเป็นคำสั่งที่เกี่ยวข้องกับ Stack ... ในคราวหน้า ก็จะเป็นคำสั่งที่เกี่ยวกับการแลกเปลี่ยนข้อมูล หรือ Exchange กันต่อไป ... หลังจากนี้คงเป็นเรื่อง การตรวจสอบเงื่อนไข , การติดต่อกับ I/O , .... และอีกมากมาย เท่าที่ยังเขียนกันได้ต่อไป
ก่อนจากในครั้งนี้ ผมอยากแนะนำสักนิดว่า ประโยชน์ที่เราได้จากการใช้ stack ก็คือ เราสามารถเอาค่าไปเก็บ หรือ ฝากเอาไว้ใน stack แล้ว สามารถ เรียกกลับมาใช้ ได้อีก ซึ่งวิธีนี้นิยมใช้กับการวนรอบที่มีการซ้อนกันหลายๆ รอบ ... นอกจากนี้ เวลาที่เรามีการเรียกใช้โปรแกรมย่อย เราก็อาศัย stack เป็นที่เก็บค่าตำแหน่งของคำสั่งที่จะต้องทำการประมวลผลหลังจากที่ ทำส่วนที่เราเรียกใช้งานเสร็จแล้ว ... ตอนนี้คงจบเท่านี้ก่อนล่ะครับ ...