[ บทความ : เรียนรู้ z80 ] ตอนที่ 11 เรื่อง คำสั่งบวกและลบ |
เรียนรู้ Z80 ตอนที่ 11
คำสั่งบวกและลบ
สวัสดีครับ หายไป 1 สัปดาห์ ตอนนี้เรามาศึกษาคำสั่งต่อไปกันดีกว่าครับ ... คำสั่งที่กล่าวถึงในครั้งนี้ จะเป็นคำสั่งเกี่ยวกับการ บวก และ ลบ ... คำสั่งบวก และลบ ของ Z80 นั้น มีรูปแบบการทำงานหัลก ๆ 2 ลักษณะ คือ บวก/ลบ แบบไม่สนใจ carry flag และ การบวก/ลบ แบบสนใจ carray flag ... การบวก/ลบ ของ Z80 นั้น จะสามารถใช้ได้กับข้อมูลแบบ 8 และ 16 บิต รูปแบบของคำสั่งเป็นดังนี้
ตาราง 11-1 ชุดคำสั่งสำหรับ บวก/ลบ ส่วนที่ 1
Instruction Source/Target Flag Operation ADD A , s [ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการทดค่าจากบิต 3 ไปบิต 4 , H=1
[ 4 ] ถ้าเกิดการ Overflow , V=1
[ 5 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 6 ] N=0 เสมอเป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+s ... ดูความหมายของ s ที่หมายเหตุ ADD A , (HL) เหมือน ADD A,s เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+ (HL) ADD A , (IX+d) เหมือน ADD A,s เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+ (IX+d) ADC A , s เหมือน ADD A,s เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+s+CY ... ดูความหมายของ s ที่หมายเหตุ ADD HL , ss [ 1 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 2 ] N=0 เสมอเป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ HL <- HL+ss ... ดูความหมายของ ss ที่หมายเหตุ ADD IX , pp เหมือน ADD HL,ss เป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ IX <- IX+pp ... ดูความหมายของ pp ที่หมายเหตุ ADD IY , rr เหมือน ADD HL,ss เป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ IY <- IY+rr ... ดูความหมายของ rr ที่หมายเหตุ ADC HL , ss [ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 4 ] ถ้าเกิดการ Overflow , V=1
[ 5 ] N=0 เสมอเป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ HL <- HL+s+CY ... ดูความหมายของ ss ที่หมายเหตุ SUB s [ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการทดค่าจากบิต 3 ไปบิต 4 , H=1
[ 4 ] ถ้าเกิดการ Overflow (ค่าเกิน 255) , P/V=1
[ 5 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 6 ] N=1 เสมอเป็นการลบเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A-s ... ดูความหมายของ s ที่หมายเหตุ SBC A, s เหมือน SUB s เป็นการลบเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A-s-CY ... ดูความหมายของ s ที่หมายเหตุ SBC HL, ss [ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 4 ] ถ้าเกิดการ Overflow , V=1
[ 5 ] N=1 เสมอเป็นการลบเลขขนาด 16 บิต โดยมีผลการทำงานคือ HL <- HL-ss-CY ... ดูความหมายของ ss ที่หมายเหตุ DAA ไม่มี [ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการทดค่าจากบิต 3 ไปบิต 4 , H=1
[ 4 ] P=1 เพื่อให้จำนวนบิตที่มีค่า 1 เป็นจำนวน คู่
[ 5 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
เป็นการเปลี่ยนข้อมูล ในรีจิสเตอร์ A ให้เป็นตัวเลข BCD ปกติ เราจะใช้คำสั่งนี้หลังจากใช้คำสั่ง ADD หรือ SUB เพื่อให้การบวก และลบ เป็นการบวกลบเลขแบบ BCD หมายเหตุ
s คือ ตัวเลขขนาด 8 บิต หรือรีจิสเตอร์ B, C, D, E, H, L และ A
ss คือ รีจิสเตอร์คู่ (pair register) อันได้แก่ BC, DE, HL และ SP
pp คือ รีจิสเตอร์คู่ (pair register) อันได้แก่ BC, DE, IX และ SP
rr คือ รีจิสเตอร์คู่ (pair register) อันได้แก่ BC, DE, IY และ SP
P/V เป็น flag register จะทำงาน 2 หน้าที่ คือ เป็น Parity Flag เมื่อทำงานกับคำสั่งทางด้าน Logic และ เป็น Overflow Flag เมื่อทำงานเกี่ยวกับการคำนวณ
มาดูตัวอย่างโปรแกรมสำหรับ คำสั่งที่เกี่ยวกับ การบวกลบข้อมูล กันครับ ...
; ; Filename : AddSub1.asz ; Author : Supachai Budsaratij (raek@se-ed.net) ; Date : March 13, 2001 ; Hardware : ET-Board V6 (Z80 Mode) ; ET-Board V5 ; ET-Board V4 ; ET-Board V3.5 NewPower ; CP-jr180 Plus ; ; 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 ;--- Case study 1 : Over flow. LD A,250 ADD A,6 ;--- Case study 2 : Subtract by greater value. LD A,5 LD B,20 SUB B ;--- Case study 3 : Add 16 bit. LD HL,00FFh LD BC,0001h ADD HL,BC ;--- Case study 4 : BCD adding. LD A,16h LD C,55h ADD A,C DAA ;--- Case study 5 : Add with carry flag operation. LD A,0FFh LD B,01h ADD A,B ADC A,B ;--- Case study 6 : Subtract with carry flag operation. LD A,05h LD B,07h SUB B LD A,09h SBC A,B HALT ENDเมื่อเราทำการแปลคำสั่งเรียบร้อยแล้ว เราก็จะได้ไฟล์ .LST ดังนี้
; ; Filename : AddSub1.asz ; Author : Supachai Budsaratij (raek@se-ed.net) ; Date : March 13, 2001 ; Hardware : ET-Board V6 (Z80 Mode) ; ET-Board V5 ; ET-Board V4 ; ET-Board V3.5 NewPower ; CP-jr180 Plus ; ; INCL "etv6.inz" ; Include 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 (Page 1) 0065 GLCD_RC1 EQU 65h ; Graphics LCD read command (Page 1) 0066 GLCD_WD1 EQU 66h ; Graphics LCD write data (Page 1) 0067 GLCD_RD1 EQU 67h ; Graphics LCD read data (Page 1) ; 0068 GLCD_WC2 EQU 68h ; Graphics LCD write command (Page 2) 0069 GLCD_RC2 EQU 69h ; Graphics LCD read command (Page 2) 006a GLCD_WD2 EQU 6Ah ; Graphics LCD write data (Page 2) 006b GLCD_RD2 EQU 6Bh ; Graphics LCD read data (Page 2) ; ; 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 8000 ORG UMEM_ORG ; Start at UMEM_ORG 8000 main ;--- Case study 1 : Over flow. 8000 3e fa LD A,250 8002 c6 06 ADD A,6 ;--- Case study 2 : Subtract by greater value. 8004 3e 05 LD A,5 8006 06 14 LD B,20 8008 90 SUB B ;--- Case study 3 : Add 16 bit. 8009 21 ff 00 LD HL,00FFh 800c 01 01 00 LD BC,0001h 800f 09 ADD HL,BC ;--- Case study 4 : BCD adding. 8010 3e 16 LD A,16h 8012 0e 55 LD C,55h 8014 81 ADD A,C 8015 27 DAA ;--- Case study 5 : Add with carry flag operation. 8016 3e ff LD A,0FFh 8018 06 01 LD B,01h 801a 80 ADD A,B 801b 88 ADC A,B ;--- Case study 6 : Subtract with carry flag operation. 801c 3e 05 LD A,05h 801e 06 07 LD B,07h 8020 90 SUB B 8021 3e 09 LD A,09h 8023 98 SBC A,B 8024 76 HALT 8025 END 0061 CLCD_RC 0063 CLCD_RD 0060 CLCD_WC 0062 CLCD_WD 0065 GLCD_RC1 0069 GLCD_RC2 0067 GLCD_RD1 006b GLCD_RD2 0064 GLCD_WC1 0068 GLCD_WC2 0066 GLCD_WD1 006a GLCD_WD2 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ส่วนรายละเอียดของไฟล์ฐานสิบหก ก็จะเป็นดังนี้ครับ
:208000003EFAC6063E0506149021FF00010100093E160E5581273EFF060180883E05060749 :05802000903E09987676 :008025015Aมา debug กันดีกว่าครับ เริ่มตั้งแต่โหลดโปรแกรมเลยนะครับ
ตัวอย่างส่วนที่ 1 : ทดสอบ Overflow
... ให้ A = FAh... บวกค่าใน A ด้วย 6 อ๊ะ ... แต่ A เป็นรีจิสเตอร์ 8 บิต ค่าสูงสุดที่ได้ก็คือ 255 (FFh) สิ แต่ FAh + 6h มันเกิน 255 ... ดูผลลัพธ์กันก่อน FAh + 6h = 100h ใช่ไหม ... เอาล่ะ ดูที่ Flag นะครับ 100h แต่ A เป็นรีจิสเตอร์ 8 บิต ดังนั้น มันเลยเอาค่า 00 มาเก็บใน A พอ A เป็น 0 , Z ก็เลยเป็น 1 ... มีการเกิด Overflow เพราะค่าเกิน 255 ดังนั้น O น่าจะเป็น 1 แต่ debug ของบอร์ด ET-V6 ไม่ยักแสดงผล (แฮะ) แต่ผมลองใช้ Hex File ตัวนี้ไปทำงานบน Simulator ค่าของ P/V ก็ เปลี่ยนเป็น 1 นะครับ ยังไง ผมจะลองสอบถาม ทาง ETT อีกครั้งนึงล่ะกันครับ ... มาดูแฟล็ก ตัวต่อมาดีกว่า ... จากการทำงานของคำสั่งนี้ เกิดการทดจากบิต 3 ไปบิต 4 (A+6 มันเกิน F) ทำให้ H=1 และมีการทดเกิดขึ้น C เลยเป็น 1 ด้วยครับ
ตัวอย่างส่วนที่ 2 : ทดสอบการลบด้วยค่าที่มากกว่า
ให้ A เก็บค่า 5ให้ B เก็บค่า 14h
ทำการลบค่าที่เก็บใน A ด้วย B ... ผลที่ได้คือ S=1 ทั้งนี้เพราะ ตัวตั้งมีค่าน้อยกว่า ตัวที่นำไปลบ ทำให้ผลลัพธ์ที่ได้เป็นค่าลบ N =1 เพราะเป็นคำสั่ง ลบ และ C เป็น 1 เนื่องจากเกิดการยืมค่า ... ง่ายใช่ไหมครับ ... เอ๋ ผลลัพธืที่ได้เป็น F1h ทำไมเป็น F1h ล่ะ 5 ลบด้วย 20 ต้องได้ -15 จริงไหม ... ลองมาคิดกันนะครับ เดิม A มีค่า 5 เขียนเป็นเลขฐานสอง คือ 00000101 , ส่วน B ก็เป็น 00010100 แต่เวลาจะลบนั้น Z80 จะแปลง ตัวที่นำไปลบ ให้เป็นค่าแบบ 2's complement ก่อน แล้วนำผลที่ได้ไปบวกกับตัวตั้ง ดังนั้น 00010100 แปลงเป็น 2's complement ได้เป็น 11101100 แล้วนำไปบวก ก็จะได้เป็น 00000101 + 11101100 = 11110001 หรือเขียนเป็นฐานสิบหก คือ F1h ใช่ไหมครับ ... (ถ้าไม่เข้าใจ ลองอ่านดูอีกรอบนึง ถ้าไม่เข้าใจก็อ่านรอบต่อๆ ไปอีกนะครับ :D) ... มาดู ตัวอย่างการบวกค่าแบบ 16 บิตกันครับ
ตัวอย่างส่วนที่ 3 : ทดสอบ บวกแบบ 16 บิต
ในการบวกแบบ 16 บิตต้องอาศัย HL ครับ ที่ AF ใช้ไม่ได้ น่าจะเป็นเพราะรีจิสเตอร์ F ใช้เก็บ สถานะการทำงานน่ะครับ ... เริ่มด้วยการกำหนดให้ HL เก็บค่า FFhเวลาบวกก็ต้องใช้ค่า 16 บิต หรือ ผ่านกับรีจิสเตอร์ 16 บิต ด้วยนะ ... งั้นใช้ BC ล่ะกันครับ ... กำหนดให้ BC เป็น 1 ล่ะกัน
มาบวกค่าดู ... ผลที่ได้คือ HL เป็น 0100h มี Sign flag เป็น 1 ทั้งนี้เพราะค้างมากจาก ตัวอย่างส่วนที่ 2 นะครับ ไม่เกี่ยวกับคำสั่งนี้
ตัวอย่างส่วนที่ 4 : ทดสอบ บวกค่าแบบ BCD
คราวนี้มาลองบวกค่าแบบ BCD กันนะครับ ... เริ่มต้นกำหนดให้ A เก็บค่า 16hกำหนดให้ C เก็บค่า 55h
บวก A ด้วย C ผลที่ได้คือ 6B ... เอ ... ไม่ใช่การบวกแบบ BCD นี่นา ... เพราะถ้าบวกแบบ BCD เราเอา 16h บวกกับ 55h น่าจะเป็น 71h สิ ... อ้อ ... ลืมไปคำสั่งนึง
เอาล่ะเรียก DAA เพื่อแปลงผลลัพธ์ให้เป็นแบบ BCD ซะเลย ... เห็นไหม สำเร็จ A กลายเป็น 71h แล้ว
ตัวอย่างส่วนที่ 5 : ทดสอบ บวกค่าด้วย carry flag
บวกแบบธรรมดาไปแล้ว ... เรามาลองแบบ ใช้ carry flag กัน ... ว่าด้วยกำหนดให้ A เก็บ FFhกำหนดให้ B เป็น 1
ทำการบวก A กับ B ได้ออกมาเป็น 00h ... ผลทำให้ CY เป็น 1 มีการทดข้ามจากบิต 3 ทำให้ H เป็น 1 และผลลัพธ์ที่ได้เป้น 0 ทำให้ Z เป็น 1 ...
เอาล่ะลองมาบวกแบบ มี carry กันครับ ตินนี้เรามี A เก็บค่า 00h , B เก็บค่า 1 และมีสถานะของ Carry flag เป็น 1 ดังผลลัพธ์ที่ได้ น่าจะเป็น 0+1+1 หรือ 02h ... เย้ .. ถูกหต้องใช่ไหม ตอนนี้ A เก็บค่า 2 เอาไว้แล้ว
ตัวอย่างส่วนที่ 6 : ทดสอบ ลบด้วย carry flag
ตวอย่างสุดท้าย เรามาทำการลบแบบสนใจ Carry flag กันบ้าง , ให้ A เก็บ 5ให้ B เก็บ 7
ทำการลบค่าใน A ด้วย B ผลที่ได้คือ A กลายเป็น FEh ส่วนผลของ flag ลองหาเหตุผลดูนะครับ ว่าเป็นเพราะอะไร
คราวนี้กำหนดให้ A เป็น 9
ลองลบ A ด้วย B แบบ carry flag ดูล่ะนะ ... ตอนนี้ A เป็น 9, B เป็น 7 และ Carry Flag ตอนนี้เป็น 1 ดังนั้น 9h - 7h - 1 ผลลัพธ์ที่ได้ คือ 01h ครับ ... ลองดูนะครับว่ามาได้อย่างไร 9 เขียนเป็นเลขฐานสองคือ 00001001 , 7 เขียนเป็นเลขฐาน 2 คือ 00000111 แปลงเป็น 2's complement ได้เป็น 11111001 บวกกันได้ 00001001 + 11111001 = 00000010, แล้ว 1 (ค่าจาก carry flag ) แปลงเป็น 2's complement ได้เป็น 11111111 ดังนั้น ผลลัพธ์สุดท้ายคือ 00000010 + 11111111 = 00000001 ... เป็นไงครับ คำตอบสุดท้าย คือ 1 (ฟู่ ... อธิบายเหนื่อยเลยครับ)
จบโปรแกรมแล้วครับ
จบล่ะครับ ... บทความตอนนี้ยาวจริงๆ ยาวทั้งตัวอย่าง และการทำงาน ... ซับซ้อนนิดหน่อยเรื่องการ ลบ ... สงสัยไหมครับ ว่าทำไม ต้องเก็บค่าแบบ 2's complement ... ทำไม ไม่กำหนดให้ บิต 7 เป้น 1 หมายถึง ค่าลบ และถ้าบิต 7 เป็น 0 เป็นค่าบวกไปเลย ... เหตุผลข้อนึง คือ 00000000 มีค่าเท่ากับ 0 ใช่ไหม แล้ว 10000000 ล่ะ ... -0 หรือเปล่าครับ ... ถ้าเราสนแค่ ให้บิต 7 เป็นตัวบอกค่า บวก หรือ ลบ ... เราก็ติดตรงที่ว่า ค่า 0 ดันสามารถ เขียนแทนได้ทั้ง 2 แบบ ... ผลที่ได้ ก็ทำให้เกิดแนวคิดของ 2's complement ไงล่ะครับ ... ขอไม่อธิบายมากกว่านี้นะครับ เดี๋ยวมีคนยิ้ม (ฮ่าๆ คำถามนี้ผมใช้ถามนักศึกษาบ่อยๆ น่ะครับ ... ขอไม่อธิบายต่อนะครับ) ... สุดท้ายนี้หวังว่า ผู้อ่านคงสามารถทำการบวก และลบ เลขเป็นแล้วนะครับ (หมายถึงสั่งให้เจ้า Z80 มันบวก/ลบเลขน่ะครับ) ... แต่สงสัยไหมครับ ว่าทำไมผมไม่พูดถึงคำสั่งคูณ และหารเลย ... คำตอบก็คือ Z80 ไม่มีคำสั่ง คูณ และหาร ไง ! อ้าว แล้วจะทำยังไงดีล่ะ ... คำตอบคือ ต้องเขียนเองครับ ... เอาไว้ผ่านคำสั่งกระโดดแบบต่าง ๆ ไปก่อน เราค่อยมาศึกษา วิธีการคูณ และหาร กันอีกครั้งนึงล่ะกันครับ ... คราวหน้าจะเป็นคำสั่งที่เกี่ยวกับการ กระโดด และคำสั่ง CP ครับ ... ต่อไปเราก็จะเขียนโปรแกรม แบบเงื่อนไข หรือ วนรอบแบบต่างๆ กันได้แล้วครับ ... สัปดหาหน้าเจอกันอีกครับ