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

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

คำสั่งที่เกี่ยวกับ stack

stack เป็นการจัดการกับหน่วยความจำรูปแบบหนึ่ง ซึ่งอาศัยหลักการทำงานที่เรียก LIFO (Last In First Out) หรือ เรียกอีกอย่างหนึ่งได้ว่า ข้อมูลที่เราใส่เข้าไปทีหลัง จะถูกนำออกมาก่อนข้อมูลที่เราใส่ไปก่อนหน้าข้อมูลชุดนั้นๆ

การกระทำที่เราใช้กับหน่วยความจำ stack นั้นมี 2 อย่าง คือ การ Push และ Pop

Push เป็นความหมายแทนการนำข้อมูลไปเก็บเอาไว้ในหน่วยความจำ stack
Pop เป็นความหมายแทนการนำข้อมูลออกมาจากหน่วยความจำ stack

นั่นหมายความว่า แต่ละครั้งที่เราสั่ง push ก็จะมีการนำข้อมูลไปซ้อนเอาไว้ด้านบนของข้อมูลที่เราได้ push ไปก่อนหน้านี้ เช่น เราได้สั่งเรียงลำดับดังต่อไปนี้


		push   5
		push   10
		push   2
		push   14

ในหน่วยความจำของ stack ก็จะเป็นดังนี้ครับ

ลำดับ
ข้อมูล
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 ดังนี้ครับ

Instruction
Source/Target
Flag
Operation
PUSH
qq
-
(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
qq
-
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
ตาราง 6-1 ชุดคำสั่งสำหรับ Stack

หมายเหตุ 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 เป็นที่เก็บค่าตำแหน่งของคำสั่งที่จะต้องทำการประมวลผลหลังจากที่ ทำส่วนที่เราเรียกใช้งานเสร็จแล้ว ... ตอนนี้คงจบเท่านี้ก่อนล่ะครับ ...



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