มาลองออกแบบ Abstract Instruction Set กัน (ตอนแรก)
ขอโทษล่วงหน้า ถ้าทำให้ใครบางคนอ่านเรื่องนี้ไม่รู้เรื่องตั้งแต่ต้น ...
บังเอิญว่า อยากจะเอาเรื่องเกี่ยวกับการออกแบบ Instruction Set ของ CPU มาให้ดู ๆ กันบ้างหนะ
Abstract Instruction Set
จุดประสงค์การออกแบบชุดคำสั่ง (Instruction Set) นี้ ไม่ใช่เพื่อให้มันประสิทธิภาพสูงสุด หรือว่าทำเป็นวงจรง่ายหรอกนะ แต่ อยากจะทำให้มันทำงานได้ครบ โดยที่มีคำสั่งน้อย ๆ มากกว่า
Chosen Design: Stack Machine
Stack Machine เป็นแนวคิดที่ว่า Operation ทั้งหลาย ทำบน Stack ... คิดว่าแบบนี้น่าจะทำให้ลดจำนวนคำสั่งลงได้เยอะ
ข้อเสียก็คือ การจะทำอะไรอย่างนึง มันต้องมีหลายคำสั่งน่ะสิ แล้วก็ การทำ Pipelining จะยากด้วย แต่ขอไม่สนเรื่องนี้ก่อน
เหตุผลสนับสนุนอีกอย่างนึงที่ทำให้เลือก Operation on Stack ก็คือ เวลาทำ compiler ให้แปล source code เป็น machine code เนี่ย มันจะค่อนข้างง่ายและตรงมาก (แต่ประสิทธิภาพก็ไม่ได้ดีเท่าไหร่แหละ)
First Two Opcodes: PUSH and POP
เวลาจะทำ Stack ใคร ๆ ก็ต้องคิดถึง Push กับ Pop แน่ ๆ เราก็กำหนดเลยละกันว่า เราจะมีสองคำสั่งนี้
ผลก็คือ เราสามารถสร้างให้คำสั่งอื่น ๆ ไม่รับ operand เองโดยตรงได้ เช่น
คำสั่ง A = B + C
Instruction Set ทั่วไป
ADD A, B, C
Stack-based Operation ของเรา
PUSH B
PUSH C
ADD
POP A
Instruction Set ทั่วไป
ADD A, B, C
Stack-based Operation ของเรา
PUSH B
PUSH C
ADD
POP A
ว่าแต่ว่า ... เราจะระบุสิ่งที่จะ PUSH กับ POP ได้ยังไงหละ?
Types of Operands
เนื่องจากเราจะลดความซับซ้อนให้เหลือน้อยที่สุด เราจะยอมให้มี operand ได้แค่แบบเดียวแบบ คือ
- Immediate
Required Registers
เราจะให้มี register เพียง 3 ตัว ที่สามารถยุ่งเกี่ยวได้ด้วย machine instruction คือ
- Program Counter (PC) = Address ของคำสั่งปัจจุบัน
- Base Pointer (BP) = Address แรกของ Local Variable ปัจจุบัน
- Stack Pointer (SP) = Address ของ Top-of-stack
สมมติว่า SP จะลดค่าเมื่อ PUSH และเพิ่มค่าเมื่อ POP นะ (ทำตามแบบที่เค้านิยมกัน)
Indirect Indexing: PBASE
เนื่องจาก Base Pointer (BP) เป็น register ที่ควรจะสามารถนำค่าไปบวก-ลบได้ ดังนั้น เราจึงควรจะสร้างคำสั่งที่ใช้อ่านค่า BP ดังนี้
PBASE = Push ค่าของ BP ขึ้น Stack
LOAD and STORE
เราจะกำหนดให้ คำสั่ง LOAD จะใช้ operand เป็นตัวบนสุดของ stack ตัวเดียว มีการทำงานคือ
- เอาค่าบนสุดของ stack ไปเป็น address อ้างอิง memory
- อ่านค่าจาก memory ในตำแหน่งนั้น
- เอาค่าที่อ่านได้ มาแทนที่ในตำแหน่งบนสุดของ stack (ที่เคยเป็น address)
- POP ตัวแรกออกจาก stack ใช้เป็น address อ้างอิง memory
- POP ตัวที่สองออกจาก stack ใช้เป็นค่าที่จะเขียนลง memory
- เขียนค่าที่ได้ (จากการ POP ครั้งที่สอง) ลงใน memory (address ได้จากการ POP ครั้งแรก)
สังเกตดี ๆ หละ ว่า จริง ๆ เราไม่ต้องมีคำสั่ง POP นะ!!!
Logical Functions: NOT, AND, OR, XOR, IFF
ถึงแม้ว่า จริง ๆ แล้วเราสามารถใช้แค่ NAND หรือ NOR เพียงอย่างเดียวได้ แต่ตรงนี้ ไม่ขอประหยัดจำนวน Opcode นะ เพราะว่ามันไม่ได้เป็นสาระสำคัญเท่าไหร่ (ถ้าอยากจะสร้างแบบประหยัด ให้มันมีแต่ NAND หรือมีแต่ NOR เวลาทำ compiler ก็จะเหนื่อยหน่อย เท่านั้นเอง)
Arithmetic Functions: NEG, ADD, SUB, MUL, DIV, MOD, EXP, LOG, ...
พวกนี้ก็เหมือนกัน ... จริง ๆ มีแค่ NEG ADD แล้วก็ MUL ก็น่าจะพอแล้ว แต่การประหยัดจำนวนคำสั่งตรงนี้ ก็ไม่ใช่สาระสำคัญเหมือนกัน ดังนั้นเราจะถือว่า คำสั่งพวกนี้ มีหมดเลย ก็แล้วกัน
Classical Modes of Addressing
เนื่องจาก Operand เรา มีแต่แบบ Immediate การอ้างถึง Operand ในลักษณะอื่น ๆ แบบที่เครื่องทั่ว ๆ ไปเค้าทำได้ มันจะต้องใช้หลายคำสั่งหน่อยนะ เช่น
Target: Value at memory address X
Abbreviation: [X]
How to PUSH that?
PUSH X
LOAD
Abbreviation: [X]
How to PUSH that?
PUSH X
LOAD
Target: Value at memory address BP + X
Abbreviation: [BP + X]
How to PUSH that?
PBASE
PUSH X
ADD
LOAD
Abbreviation: [BP + X]
How to PUSH that?
PBASE
PUSH X
ADD
LOAD
Target: Value at memory address (BP + value at X)
Abbreviation: [BP + [X]]
How to PUSH that?
PBASE
PUSH X
LOAD
ADD
LOAD
Abbreviation: [BP + [X]]
How to PUSH that?
PBASE
PUSH X
LOAD
ADD
LOAD
Target (in C): Array[Index]
Abbreviation: [BP + array + [BP + index]]
How to PUSH that?
PBASE
PUSH array
ADD
PBASE
PUSH index
ADD
LOAD
ADD
LOAD
Abbreviation: [BP + array + [BP + index]]
How to PUSH that?
PBASE
PUSH array
ADD
PBASE
PUSH index
ADD
LOAD
ADD
LOAD
Simple Branch Instructions: JMP, JPOS, JNEG, JZ, JNZ
- JMP: POP → PC
- JPOS: POP → เก็บไว้ แล้ว POP อีกที ถ้าได้ค่าเป็นเลขบวก (มากกว่า 0) ถึงจะกระโดดไปยัง address ที่เก็บไว้
- JNEG: คล้าย ๆ กับ JPOS แต่จะกระโดดถ้าค่าที่ POP ออกมาครั้งที่ 2 เป็นเลขติดลบ
- JZ: POP → เก็บไว้ แล้วก็ POP อีกที ถ้าได้ค่าเป็น 0 ถึงจะโดดไปยัง address ที่เก็บไว้
- JNZ: คล้าย ๆ กับ JZ แต่เงื่อนไขการกระโดดตรงกันข้าม
Subroutine: CALL, RETURN
คำสั่ง CALL จะวุ่น ๆ หน่อย เพราะมีการทำงานหลายขั้น คือ
- POP ค่า address ที่จะกระโดดไป เก็บไว้
- PUSH ค่า PC + 1 ไว้
- PUSH ค่า BP ไว้
- กำหนดค่าให้ BP = SP
- กำหนดค่าให้ PC = address ที่ POP ไว้ตอนแรก (ซึ่งก็คือ การกระโดดนั่นเอง)
- POP ค่า Return Code ออกมาเก็บไว้ก่อน
- POP ค่ามาใส่ไว้ใน BP
- POP อีกค่ามาใส่ไว้ใน PC (ซึ่งก็คือ การกระโดดกลับ)
- PUSH ค่า Return Code คืนลงไปใน Stack
Local Variable and Parameters
ถ้าต้องการส่งผ่าน parameter ให้กับ function เราก็จะใช้วิธี PUSH ใส่ stack ไว้ก่อน เช่น
- PUSH A
- PUSH B
- PUSH CustomAdd
- CALL
- ถ้า BP หลัง CALL = x
- SP ก่อน CALL จะ = x + 1
- ตำแหน่งของ B ก็เลย = x + 2
- และ ตำแหน่งของ A ก็ = x + 3
CustomAdd:
- PBASE
- PUSH 2
- SUB
- LOAD
- PBASE
- PUSH 3
- SUB
- LOAD
- ADD
- RETURN
ชุดคำสั่งเล็กสุด
PUSH
PBASE
LOAD
STORE
JPOS
CALL
RETURN
NAND
NEG
ADD
MUL
PUSH
PBASE
LOAD
STORE
JPOS
CALL
RETURN
NAND
NEG
ADD
MUL
คำสั่งลดความเหนื่อย
JMP
JNEG
JZ
JNZ
NOT
AND
OR
XOR
IFF
SUB
DIV
MOD
...
JMP
JNEG
JZ
JNZ
NOT
AND
OR
XOR
IFF
SUB
DIV
MOD
...
ตอนนี้เราก็ได้ Instruction Set คร่าว ๆ แล้ว แต่เราไม่พูดถึงขนาดของข้อมูลในแต่ละช่องของ Stack เลยนะเนี่ย ... ก็เลยเรียกว่า Abstract Instruction Set ไงหละ
2 Comments:
งุงุ
Hello! Quick question that's entirely off topic.
Do you know how to make your site mobile friendly? My site looks
weird when viewing from my iphone 4. I'm trying to
find a theme or plugin that might be able to resolve this problem.
If you have any recommendations, please share. With thanks!
Have a look at my homepage somatodrol blog
Post a Comment
<< Home