คำถาม 1 — Bus & ทิศทางสัญญาณ 10 คะแนน
ข้อนี้วัดสองเรื่อง: ทิศทางของสายแต่ละเส้น (ข้อ 1–4) และ ใครเป็นคนขับ data bus กับความจุของหน่วยความจำ (ข้อ 5–8) ลองกดสลับสถานะ read/write ในแล็บด้านบนดูก่อน แล้วค่อยไล่ทีละข้อ
รูปที่ 1 — แผนภาพแสดงการต่อเชื่อมระหว่าง CPU และ Memory
Lab 01 ไล่ทิศทางสัญญาณบนบัส
read active
write active
ไม่ active ทั้งคู่
active ทั้งคู่
Memory
CPU A
address bus · 32 บิต · ทางเดียว
data bus · 32 บิต · สองทาง
read
write
ไล่ทีละข้อย่อย
1.1
Address bus ทิศทางไหน ตอบสั้น
คำตอบ CPU A ไป Memory — ทางเดียว
ทำไม CPU เป็นฝ่ายกำหนดว่าจะยุ่งกับช่องหน่วยความจำไหน ส่วน Memory เป็นฝ่ายถูกเลือกอย่างเดียว ไม่เคยเป็นคนบอก address เอง ⇒ ข้อมูลบนสายชุดนี้ไหลจาก CPU ไป Memory ทางเดียวเสมอ
ถ้าพลิกโจทย์ เพิ่ม CPU B หรือ DMA ที่เป็น bus master ได้ → address bus กลายเป็น สองทางในเชิงระบบ (ยังทางเดียวเมื่อมองจาก master ตัวเดียว) และต้องมี arbiter มาตัดสินว่าใครได้ใช้ถามว่า “ค่าบน address bus เป็นเท่าไร” → ตอบได้เสมอ เพราะ CPU เป็นคนใส่ค่าเอง ต่างจาก data bus ที่ตอบไม่ได้
1.2
Data bus ทิศทางไหน ตอบสั้น
คำตอบ สองทาง (both)
ทำไม จังหวะ read Memory ส่งข้อมูลกลับมาให้ CPU · จังหวะ write CPU ส่งข้อมูลไปให้ Memory — ใช้ สายชุดเดียวกัน สลับกันตามจังหวะ จึงต้องมี tri-state buffer คอยคุมว่าใครได้ขับ ไม่งั้นชนกัน
ถ้าพลิกโจทย์ ถ้าโจทย์แยกเป็น data-in กับ data-out คนละชุด → แต่ละชุดกลายเป็น ทางเดียว และไม่ต้องมี tri-state บนสายข้อมูลถามว่าทำไมไม่แยกสายไปเลย → เพื่อประหยัดจำนวนขาชิปและสายบนบอร์ด แลกกับการที่ใช้ได้ทีละทิศทาง
1.3
Read ทิศทางไหน ตอบสั้น
คำตอบ CPU A ไป Memory — ทางเดียว
ทำไม read เป็น สายควบคุม ไม่ใช่สายข้อมูล — CPU เป็นคนสั่งว่า “ขออ่าน” แล้ว Memory ทำตาม ⇒ สัญญาณวิ่งจาก CPU ไป Memory ทางเดียว อย่าสับสนกับ “ทิศทางของข้อมูลที่ถูกอ่าน” ซึ่งวิ่งสวนทางบน data bus
ถ้าพลิกโจทย์ โจทย์เพิ่มสาย ready / wait / ack → สายพวกนี้อุปกรณ์เป็นคนส่งกลับ ⇒ ทิศทาง Memory ไป CPU โจทย์เพิ่มสาย Int → อุปกรณ์เป็นคนส่ง ⇒ Memory/Device ไป CPU เช่นกัน
1.4
Write ทิศทางไหน ตอบสั้น
คำตอบ CPU A ไป Memory — ทางเดียว
ทำไม เหตุผลเดียวกับ read ทุกประการ — เป็นสายควบคุมที่ CPU ใช้สั่ง Memoryสรุปกฎรวบยอด: ในรูปนี้มีแค่ data bus เส้นเดียวที่เป็นสองทาง นอกนั้น (address, read, write) เป็นทางเดียวจาก CPU ทั้งหมด
ถ้าพลิกโจทย์ ถามว่าถ้าใช้สายเดียวแทน read กับ write ได้ไหม → ได้ ใช้สาย R/W̄ เส้นเดียว (1 = อ่าน, 0 = เขียน) แต่ต้องมีสาย enable เพิ่มเพื่อบอกว่าตอนนี้มี transaction จริงหรือเปล่า
1.5
read active · addr 0x20004000 เขียนอธิบาย
คำตอบ — ต้องตอบให้ครบ 2 ส่วน ① ใครขับ data bus: Memory เป็นตัวเขียน (drive) ลง data bus — CPU เป็นฝ่ายอ่าน② ค่าบน data bus เป็นเท่าไร: ระบุไม่ได้จากข้อมูลที่โจทย์ให้ เพราะมันคือ “เนื้อข้อมูล 32 บิตที่ถูกเก็บอยู่ที่ช่อง 0x20004000” ซึ่งโจทย์ไม่ได้บอกว่าในหน่วยความจำมีอะไรอยู่
ขยายความให้ได้เต็มคะแนน สาย read active แปลว่า CPU ร้องขอข้อมูล ⇒ Memory ถอดรหัส address 0x20004000 แล้ว ขับข้อมูล 32 บิต ออกมาบน data bus ให้ CPU latch เข้าไป · ในจังหวะนี้ CPU ต้องปล่อย data bus เป็น Hi-Z ไม่งั้นชนกัน ค่าที่ปรากฏจึงเป็น “ค่าที่เก็บไว้ในหน่วยความจำ” ไม่ใช่ตัวเลขที่คำนวณจาก address ⇒ ตอบเป็นตัวเลขไม่ได้
กับดัก ตอบแค่ “Memory” แล้วจบ = ได้ครึ่งเดียว ต้องเขียนด้วยว่า ค่าระบุไม่ได้และเพราะอะไร · และอย่าเผลอตอบว่า data bus = 0x20004000 (นั่นคือค่าบน address bus)
ถ้าพลิกโจทย์ read และ write inactive ทั้งคู่ → ไม่มีใครขับ ⇒ data bus เป็น Hi-Z / floating / ค่าไม่นิยาม และ Memory ไม่ถูกเข้าถึงเลยread และ write active พร้อมกัน → ผิดกติกา ⇒ bus conflict ทั้ง CPU และ Memory ขับพร้อมกัน ค่าไม่นิยาม อาจเสียหายถามค่าบน address bus → 0x2000 4000 ตอบได้เลยถามว่าอ่านได้กี่ไบต์ต่อครั้ง → data bus 32 บิต ⇒ 4 ไบต์
1.6
write active · addr 0x20008000 เขียนอธิบาย
คำตอบ ① CPU A เป็นตัวเขียนลง data bus — Memory เป็นฝ่ายรับไปบันทึกลงช่อง 0x20008000② ค่า = ระบุไม่ได้ เพราะเป็นข้อมูล 32 บิตที่โปรแกรมสั่งให้เขียน ซึ่งโจทย์ไม่ได้ให้มา
ทำไมถึงตอบตรงข้ามกับข้อ 1.5 ดูที่สายควบคุมอย่างเดียวพอ — write active แปลว่า CPU กำลัง “ยัดของ” ลง bus ⇒ CPU ขับ · ส่วน Memory เปลี่ยนเป็นฝ่าย latch เข้าไปเก็บท่องให้ติด: read → Memory ขับ · write → CPU ขับ
ถ้าพลิกโจทย์ โจทย์ให้ค่ามาด้วย เช่น “CPU เขียนค่า 0x1234ABCD” → คราวนี้ตอบตัวเลขได้ = 0x1234ABCDถามว่าเขียนลงที่ไหน → ช่องหน่วยความจำที่ address 0x20008000 (4 ไบต์ ตั้งแต่ 0x20008000 ถึง 0x20008003)
1.7
address 5 บิต · data 8 บิต อ้างได้กี่ไบต์ ตอบสั้น
คำตอบ 32 ไบต์
วิธีคิด address bus n บิต ⇒ อ้างได้ 2ⁿ ช่อง · ในที่นี้ 2⁵ = 32 ช่อง data bus 8 บิต ⇒ ช่องละ 1 ไบต์ ⇒ 32 × 1 = 32 ไบต์
กับดัก สาย read และ write ที่โจทย์บอกว่า “อย่างละ 1 bit” เป็น ตัวหลอก ไม่เกี่ยวกับความจุเลย · และสูตร ไม่ใช่ 2^addr × 2^data
ถ้าพลิกโจทย์ data bus 16 บิต, address 10 บิต → ถ้า 1 ช่อง = 1 word 16 บิต ⇒ 2¹⁰ × 2 = 2048 ไบต์ · ถ้า byte-addressable ⇒ 2¹⁰ = 1024 ไบต์ (อ่านโจทย์ให้ขาด)ถามกลับเป็น “ต้องใช้กี่บิตถึงจะอ้าง 1 MB” → 2ⁿ = 2²⁰ ⇒ 20 บิต ถามเป็นบิตแทนไบต์ → 32 ไบต์ × 8 = 256 บิต
1.8
address 6 บิต · data 8 บิต อ้างได้กี่ไบต์ ตอบสั้น
คำตอบ 64 ไบต์
วิธีคิด 2⁶ = 64 ช่อง × 1 ไบต์ = 64 ไบต์ — เพิ่ม address มา 1 บิต ความจุ เพิ่มเป็น 2 เท่า เสมอ
ตารางที่ควรจำ 2⁵ = 32 · 2⁶ = 64 · 2¹⁰ = 1 K · 2¹² = 4 K · 2¹⁶ = 64 K · 2²⁰ = 1 M · 2²⁴ = 16 M · 2²⁸ = 256 M · 2³⁰ = 1 G · 2³² = 4 G
ถ้าพลิกโจทย์ address 24 บิต → 16 MB · address 32 บิต → 4 GB · address 28 บิต → 256 MBถามย้อน “อยากได้ 1 GB ด้วย data bus 32 บิตแบบ word-addressable” → 1 GB ÷ 4 = 2²⁸ ช่อง ⇒ 28 บิต
คำถาม 2 — Bus Arbitration 10 คะแนน
ข้อ 1 นับเส้นจากรูป · ข้อ 2–4 ถามว่า “ใครเขียน ได้” · ข้อ 5–7 ถามว่า “ใครอ่าน ได้” — สองกลุ่มนี้คำตอบต่างกันคนละขั้ว และข้อ 8 คือข้อเขียนอธิบายที่ต้องตอบให้ครบ 4 ชั้น
รูปที่ 2 — แถวบนคือ bus ร่วม (นับเส้นได้ 8 เส้น) · สามเหลี่ยมคือ tri-state buffer · แถวล่างคือ dedicated lines ที่ใช้แจกสิทธิ
Lab 02 ใครเขียนได้ ใครอ่านได้
กดเลือกอุปกรณ์ที่ได้สิทธิเขียน — หรือกด “ให้สิทธิทุกตัว” เพื่อดูว่าเกิดอะไรขึ้น
Device A
Device B
Device C
ให้สิทธิทุกตัวพร้อมกัน
Device B ทำหน้าที่เป็น bus arbiter
ไล่ทีละข้อย่อย
2.1
ระบบ bus นี้กี่บิต ตอบสั้น
คำตอบ 8 บิต
วิธีคิด นับเส้นสัญญาณของ bus ในรูปที่ 2 ตรงๆ — แถวบนที่ทุกอุปกรณ์ต่อร่วมกันมี 8 เส้น ⇒ ส่งข้อมูลได้ครั้งละ 8 บิต อย่านับรวมสายแถวล่าง เพราะนั่นคือ dedicated lines ที่ใช้แจกสิทธิ ไม่ใช่ตัว bus
ถ้าพลิกโจทย์ รูปวาดมา 16 เส้น → ตอบ 16 บิต (ข้อนี้วัดการนับเส้นล้วนๆ อย่าคิดเยอะ)ถามว่า bus 8 บิต ส่งข้อมูล 32 บิตต้องใช้กี่รอบ → 32 ÷ 8 = 4 รอบ ถาม bandwidth ที่ 50 MHz → 50 M × 1 ไบต์ = 50 MB/s
2.2
Device A ได้สิทธิ → ใครเขียนได้ ตอบสั้น
คำตอบ Device A เท่านั้น
ทำไม การ “เขียนลง bus” คือการ ขับ (drive) สัญญาณ ⇒ ต้องมีตัวเดียวเท่านั้น ไม่งั้นชนกัน ตัวที่ไม่ได้สิทธิจะถูก tri-state buffer บังคับให้อยู่สถานะ Hi-Z คือปล่อยสายลอย ไม่ยุ่งกับ bus
ถ้าพลิกโจทย์ ถามว่าตัวที่ไม่ได้สิทธิทำอะไรได้บ้าง → อ่านได้ แต่เขียนไม่ได้ถามว่าใครเป็นคนตัดสินว่า A ได้สิทธิ → bus arbiter ซึ่งในรูปนี้คือ Device B
2.3
Device B ถือสิทธิ → ใครเขียนได้ ตอบสั้น
คำตอบ Device B เท่านั้น
กับดักของข้อนี้ B เป็น arbiter ก็จริง แต่ arbiter คือ “คนแจกสิทธิ ” ไม่ใช่ “คนที่เขียนได้ตลอดเวลา” — ตอนที่ B ถือสิทธิ B ก็เขียนได้ตัวเดียวเหมือนคนอื่น ⇒ ตอบ B ตัวเดียว ไม่ใช่ B รวมกับใครอีก
ถ้าพลิกโจทย์ ถ้า arbiter (B) พัง → ไม่มีใครแจกสิทธิ ⇒ อาจไม่มีตัวไหนเขียนได้เลย (ระบบค้าง) หรือหลายตัวเขียนพร้อมกันจนเกิด contentionถามว่า arbiter ต้องเป็น CPU เสมอไหม → ไม่จำเป็น เป็นวงจรแยกก็ได้ โจทย์เองก็เขียนว่า “อาจจะเป็น CPU”
2.4
Device C ได้สิทธิ → ใครเขียนได้ ตอบสั้น
คำตอบ Device C เท่านั้น
สรุปข้อ 2.2–2.4 คำตอบเป็นแพตเทิร์นเดียวกันหมด: ใครถือสิทธิ คนนั้นเขียนได้คนเดียว ไม่ว่าตัวนั้นจะเป็น arbiter หรือไม่
ถ้าพลิกโจทย์ มีอุปกรณ์ 5 ตัว ต้องใช้ dedicated line กี่เส้น → แบบ 1 เส้นต่ออุปกรณ์ = 5 เส้น · ถ้าเข้ารหัสเป็นเลขฐานสอง = ⌈log₂5⌉ = 3 เส้น ถามว่ามีวิธี arbitration แบบไหนบ้าง → Daisy chain (ส่งสิทธิเป็นทอดๆ ตัวใกล้ได้เปรียบ เกิด starvation) · Centralized / dedicated lines (แบบในรูปนี้) · Distributed · Round-robin (ยุติธรรม แก้ starvation)
2.5
Device A ได้สิทธิ → ใครอ่านได้ ตอบสั้น
คำตอบ Device A, B และ C — ทุกตัว
ทำไมคำตอบพลิกจากข้อ 2.2 การ “อ่าน” คือแค่ วัดแรงดันบนสาย (sense) ไม่ได้ขับสัญญาณ จึงไม่รบกวนใครและไม่ต้องขอสิทธิ bus เป็น broadcast — ทุกตัวที่ต่ออยู่เห็นค่าเดียวกันหมดในเวลาเดียวกัน รวมถึง Device A ที่กำลังเขียนเองด้วย
กับดัก อย่าตอบว่า “B กับ C” โดยตัด A ออก — ตัวที่เขียนก็อ่านค่าที่ตัวเองขับได้ และในทางปฏิบัติมักใช้อ่านกลับเพื่อตรวจสอบด้วยซ้ำ
2.6
Device B ถือสิทธิ → ใครอ่านได้ ตอบสั้น
คำตอบ Device A, B และ C — ทุกตัว
ทำไม คำตอบไม่ขึ้นกับว่าใครถือสิทธิเลย เพราะสิทธิคุมแค่ “การเขียน” ไม่ได้คุม “การอ่าน”
2.7
Device C ได้สิทธิ → ใครอ่านได้ ตอบสั้น
คำตอบ Device A, B และ C — ทุกตัว
จำแค่ประโยคเดียวจบทั้ง 6 ข้อ เขียนได้ทีละตัว · อ่านได้ทุกตัวเสมอ
ถ้าพลิกโจทย์ ถามว่าจะทำให้อุปกรณ์บางตัวอ่านไม่ได้ต้องทำยังไง → ต้องเพิ่มสาย chip-select / output-enable ฝั่งรับ หรือแยก bus ออกเป็นคนละเส้น — ตัว bus ร่วมเองทำไม่ได้
2.8
ถ้า A, B, C เขียนพร้อมกัน จะเกิดอะไรขึ้น เขียนอธิบาย
คำตอบ — เขียนให้ครบ 4 ชั้นนี้ ① ชื่อปรากฏการณ์: เกิด Bus contention / bus conflict — มี driver หลายตัวขับเส้นเดียวกันไปคนละระดับลอจิกในเวลาเดียวกัน② ผลต่อข้อมูล: แรงดันบนเส้นถูกดึงไปค้างอยู่ระหว่าง 0 กับ 1 ⇒ เป็นค่า undefined ทุกตัวที่อ่านจะได้ข้อมูลผิด ระบบทำงานผิดพลาด③ ผลต่อฮาร์ดแวร์: ตัวหนึ่งดันขึ้น VDD อีกตัวดึงลง GND ⇒ เกิดเส้นทาง ลัดวงจร กระแสสูงไหลผ่าน output driver ⇒ ร้อน กินไฟ และ ชิปเสียหายถาวรได้ ④ วิธีป้องกัน: ใช้ arbiter คู่กับ tri-state buffer บังคับให้ตัวที่ไม่ได้สิทธิอยู่สถานะ Hi-Z (คือสามเหลี่ยมที่เห็นในรูปที่ 2) หรือใช้ open-drain + pull-up
เขียนสั้นๆ ก็ได้แบบนี้ “เกิด bus contention — driver หลายตัวขับสายเดียวกันคนละลอจิก ทำให้ค่าบน bus ไม่นิยาม ทุกตัวอ่านได้ข้อมูลผิด และเกิดกระแสลัดวงจรผ่าน output driver จนชิปอาจเสียหาย ป้องกันด้วย arbiter + tri-state buffer ให้ตัวที่ไม่ได้สิทธิอยู่ Hi-Z”
ถ้าพลิกโจทย์ ถ้าใช้ open-drain แทน tri-state → ไม่เกิดลัดวงจร (ไม่มีใครดัน HIGH) แต่ผลลัพธ์บนสายกลายเป็น wired-AND ของทุกตัว ⇒ ข้อมูลยังผิดอยู่ดีถ้ามีแค่ 2 ตัวเขียนพร้อมกันแต่ค่าตรงกัน → บังเอิญไม่เห็นข้อผิดพลาดบนข้อมูล แต่ยังเป็นการออกแบบที่ผิดและกระแสยังผิดปกติถามว่า starvation คืออะไร → อุปกรณ์ที่ priority ต่ำไม่ได้สิทธิสักที แก้ด้วย round-robin หรือ aging
คำถาม 3 — Interrupt & Memory Map 10 คะแนน
ก่อนตอบข้อย่อยไหนก็ตาม ให้ ถอด address map จากรูปก่อนเสมอ — ทำครั้งเดียวใช้ตอบได้ทั้งข้อ 3.3 ถึง 3.5 ลองพิมพ์ address ลงหัววัดด้านบนเพื่อเช็คคำตอบได้
รูปที่ 3 — Main Memory เป็น volatile · Boot Mem เป็น non-volatile · เส้นแดง = สาย interrupt · เส้นฟ้าบาง = chip-select จาก decoder
ถอด address map จากรูปที่ 3 — ทำก่อนตอบข้อย่อย สูตร 4 ขั้น ใช้ได้กับทุกวงจรที่โจทย์ให้ ① ดูขา CS ของทุกกล่อง ว่ารับสายอะไร และมี วงกลม ○ คั่นไหม — มี ○ = ต้องเป็น 0 · ไม่มี ○ = ต้องเป็น 1 · หลายขา = ต้อง active ครบทุกขา② Decoder ทำงานเมื่อ CS ครบ แล้ว X1X0 เลือก Y0–Y3 (00→Y0, 01→Y1, 10→Y2, 11→Y3)③ ไล่เส้น Y ว่าไปเข้า CS ของกล่องไหน (ในรูปสายไขว้กัน ต้องไล่ทีละเส้น)④ กล่องรับ A[k-0] กี่บิต ⇒ ขนาดของมัน = 2^(k+1) ไบต์
hex หลักแรก อุปกรณ์ ช่วง address เงื่อนไขจากรูป
0–7 Main Memory 0x00000000–0x7FFFFFFF CS1, CS0 มีวงกลม ○ ทั้งคู่และรับ A[31] ⇒ ต้อง A[31] = 0
8–B DMA 0x80000000–0xBFFFFFFF CS1 ← A[31], CS0 ○← A[30] ⇒ A[31]=1 และ A[30]=0
C Boot Mem 0xC0000000–0xCFFFFFFF Decoder เปิดเมื่อ A[31]=A[30]=1 X1X0 = A[29]A[28] Y0→Boot · Y1→Dev0 · Y2→Dev1 · Y3→Dev2 ตัวละ 256 MB (A[27-0])
D Dev 0 0xD0000000–0xDFFFFFFF
E Dev 1 0xE0000000–0xEFFFFFFF
F Dev 2 0xF0000000–0xFFFFFFFF
ทริกจำ 5 วินาที ดู hex หลักแรก ตัวเดียวพอ — 0-7 Main 8-B DMA C Boot D Dev0 E Dev1 F Dev2
Lab 03 หัววัด address — วงจรรูปที่ 3 (ของคำถามนี้)
0x00001000
0x05000000
0x90000000
0xCFFF0000
0xE0000100
0xF0000000
แผนที่นี้เป็นของ วงจรรูปที่ 3 เท่านั้น — คำถาม 6 ใช้วงจรคนละตัว (รูปที่ 4) และมี address map คนละแบบ
Lab 07 ไทม์ไลน์ interrupt — ดูว่าช่อง (a)(b)(c)(d) มาจากไหน
ข้อ 3.3
ข้อ 3.4
ข้อ 3.5
เดินขั้นถัดไป →
เริ่มใหม่
ไล่ทีละข้อย่อย
3.1
อุปกรณ์ไหน interrupt CPU ได้บ้าง ตอบสั้น
คำตอบ DMA และ Dev 2 เท่านั้น
วิธีดูจากรูป ไล่ เส้นสีแดง ในรูปที่ 3 — มันออกจากขา Int ของ Dev 2 (ซ้ายล่าง) และของ DMA (ขวาล่าง) วิ่งเข้า OR-gate แล้วเข้าขา Interrupt ของ CPU กล่องที่เหลือ (Decoder, Main Memory, Boot Mem, Dev 0, Dev 1) ไม่มีขา Int เลย ⇒ ส่ง interrupt ไม่ได้ตอบตามสายที่เห็นในรูปเท่านั้น อย่าเดาจากชื่ออุปกรณ์ว่าตัวไหน “น่าจะ” interrupt ได้
ถ้าพลิกโจทย์ ถ้าโจทย์ย้ายสาย Int ของ Dev 0 มาต่อเพิ่ม → คำตอบเปลี่ยนเป็น DMA, Dev 2, Dev 0 ทันที — ข้อนี้วัดการอ่านรูปล้วนๆถามว่าทำไมต้อง OR-gate → CPU มีขา Interrupt ขาเดียว จึงต้องรวมสัญญาณจากทุกตัวเข้าด้วยกันถามว่าถ้า Dev 2 กับ DMA เรียกพร้อมกัน CPU รู้ไหมว่าใครเรียก → ไม่รู้ OR-gate บอกได้แค่ “มีคนเรียก” ⇒ ISR ต้องไล่ poll status ของแต่ละตัว หรือใช้ vectored interrupt / priority encoder แยกสาย
3.2
ถ้าไม่ใช้ interrupt CPU จะรู้ได้ยังไงว่าอุปกรณ์ทำเสร็จ เขียนอธิบาย
คำตอบ ใช้ Polling (programmed I/O หรือ busy-waiting): CPU วนลูปอ่าน status register ของอุปกรณ์ผ่าน memory-mapped I/O ซ้ำๆ แล้วตรวจบิต ready/done ว่าขึ้นหรือยัง ถ้ายังก็วนอ่านใหม่ ถ้าขึ้นแล้วจึงไปอ่านข้อมูลผลลัพธ์
โครงโปรแกรม polling poll: lw $t0, 0($s0) # $s0 = addr ของ status register
andi $t0, $t0, 1 # แยกเอาบิต DONE
beq $t0, $zero, poll # ยังไม่เสร็จ → วนใหม่
lw $t1, 4($s0) # เสร็จแล้ว → อ่านข้อมูล
ต้องเขียนข้อดีข้อเสียด้วยถึงจะเต็ม ข้อเสีย: เปลือง CPU cycle ไปกับการถามซ้ำ (busy-waiting) ทำงานอื่นไม่ได้ · ถ้าเว้นช่วง poll ห่างก็ตอบสนองช้า (latency สูง) · อุปกรณ์ยิ่งเยอะยิ่งไม่ scaleข้อดี: ฮาร์ดแวร์ง่ายมาก ไม่ต้องมีสาย Int, ISR หรือ vector table และเวลาที่ใช้คาดเดาได้
ถ้าพลิกโจทย์ ถามว่า interrupt ต่างจาก polling ยังไง → ต่างที่ ใครเป็นคนเริ่ม : polling = CPU ถามเอง · interrupt = อุปกรณ์เรียก CPU เมื่อพร้อมถามว่า DMA ต่างจากทั้งสองยังไง → DMA ย้ายข้อมูลเองโดยไม่ผ่าน CPU แล้วค่อย interrupt บอกตอนจบถามว่าเมื่อไรควรใช้ polling → เมื่ออุปกรณ์เร็วมากหรือมีตัวเดียว และต้องการความแน่นอนของเวลา (real-time ง่ายๆ)
3.3
interrupt ก่อนโหลดคำสั่งที่ 0x00001000 · vector 0xCFFF0000 4 ช่องย่อย
คำตอบ (a) 0xCFFF0000 · (b) 0xCFFF0000 · (c) Boot Memory · (d) 0x00001000
กติกา 4 ช่อง จำแค่นี้ใช้ได้ทั้ง 3 ข้อ (a) คำสั่งถัดไป = ค่า interrupt vector เพราะ CPU กระโดดไป ISR ทันที(b) ISR อยู่ที่ = ค่า vector เดียวกัน (โจทย์ชุดนี้ vector ชี้ตรงไปที่ ISR เลย)(c) เอา vector ไป เทียบ address map — 0xCFFF0000 หลักแรกเป็น C ⇒ Boot Mem(d) จบ ISR กลับไปทำ คำสั่งเดิมที่ยังไม่ได้ทำ = 0x00001000 เพราะโจทย์เขียนว่า “ก่อนจะโหลด คำสั่งที่ …” ⇒ คำสั่งนั้นยังไม่ถูก execute จึงไม่ต้อง +4
ถ้าพลิกโจทย์ เปลี่ยนเป็น “หลังจากทำคำสั่งที่ 0x00001000 เสร็จแล้วจึงได้ interrupt” → (d) = 0x00001004 (บวก 4)ถ้า CPU ปิดรับ interrupt (masked / disabled) → ไม่กระโดดเลย (a) = 0x00001000 และไม่มี ISRvector = 0x90000000 → หลักแรก 9 ⇒ DMA ⇒ ผิดปกติ เพราะ ISR ต้องอยู่ในหน่วยความจำ ไม่ใช่ในอุปกรณ์ I/O
3.4
interrupt ก่อนโหลดคำสั่งที่ 0xCFFE1000 · vector 0x00010000 4 ช่องย่อย
คำตอบ (a) 0x00010000 · (b) 0x00010000 · (c) Main Memory · (d) 0xCFFE1000
วิธีคิด ใช้กติกา 4 ช่องเดิมทุกประการ — สิ่งที่เปลี่ยนคือ vector อยู่ที่ 0x00010000 ซึ่ง หลักแรกเป็น 0 ⇒ A[31] = 0 ⇒ ตกใน Main Memory ส่วนคำสั่งที่ค้างอยู่ 0xCFFE1000 อยู่ใน Boot Mem แต่ไม่เกี่ยว — ช่อง (c) ถามถึงที่อยู่ของ ISR ไม่ใช่ที่อยู่ของคำสั่งเดิม
เฉลยของอาจารย์ข้อนี้พิมพ์ผิด ในไฟล์เฉลย ช่อง (a) พิมพ์ไว้ว่า 0x00001000 ซึ่งน่าจะก๊อปมาจากข้อ 3.3 — เพราะ (a) ต้องเท่ากับ vector เสมอ อย่างที่เห็นชัดในข้อ 3.3 และ 3.5 ที่ (a) = (b) ⇒ ค่าที่ถูกต้องคือ 0x00010000 ถ้าเจอในห้องสอบให้ตอบค่านี้แล้วเขียนกำกับว่า “= interrupt vector”
ถ้าพลิกโจทย์ สลับให้ vector = 0xE0000100 → หลักแรก E ⇒ Dev 1 · vector = 0xD0000000 → Dev 0 ถามว่าทำไม ISR ไม่ควรอยู่ในอุปกรณ์ I/O → เพราะ I/O ไม่ได้เก็บคำสั่งไว้ให้ fetch ได้ตามปกติ และมักอ่านแล้วค่าเปลี่ยน
3.5
interrupt ก่อนโหลดคำสั่งที่ 0x00410080 · vector 0x05000000 4 ช่องย่อย
คำตอบ (a) 0x05000000 · (b) 0x05000000 · (c) Main Memory · (d) 0x00410080
วิธีคิด vector = 0x05000000 หลักแรกเป็น 0 ⇒ A[31] = 0 ⇒ Main Memory คำสั่งที่ค้าง 0x00410080 ก็อยู่ใน Main Memory เหมือนกัน แต่ไม่ได้ทำให้คำตอบเปลี่ยน — (d) ยังเป็น address เดิมอยู่ดี
สรุปแพตเทิร์นทั้ง 3 ข้อ (a) และ (b) = vector เสมอ · (c) = เอา vector ไปเทียบ map · (d) = address เดิมที่ค้างอยู่ — ไม่มีข้อไหนหลุดจากแพตเทิร์นนี้เลย
ถ้าพลิกโจทย์ โจทย์ถามเพิ่มว่า CPU ต้องเก็บอะไรไว้ก่อนกระโดด → เก็บ PC เดิม (ที่จะกลับมา) พร้อม status register และ register ที่ ISR จะใช้ ลง stack หรือ EPCถามลำดับการทำงานเต็มๆ → ① ทำคำสั่งปัจจุบันให้จบ ② เช็คว่าเปิดรับ interrupt ③ เก็บ PC + สถานะ ④ กระโดดไป vector ⑤ ทำ ISR ⑥ เคลียร์ flag ที่อุปกรณ์ ⑦ คืนค่าแล้วกลับ PC เดิม
คำถาม 4 — เขียน MIPS Assembly แทน C 10 คะแนน
โจทย์กำหนด $s0=a · $s1=b · $s2=i · $s3 = base address ของ array A (int = 4 ไบต์) — ข้อ (a)–(c) เป็นคำสั่งเดี่ยวๆ ส่วน (d) และ (e) ต้องประกอบเงื่อนไขด้วย slt เอง
ตารางแปลง pseudo → คำสั่งจริง — ต้องท่องก่อนทำข้อนี้
โจทย์บอกไว้ชัด ใช้ pseudo-instruction หัก 1 คะแนนต่อข้อ ⇒ ห้ามใช้ li, move, la, blt, bgt, ble, bge, beqz, bnez
Pseudo เขียนแทนด้วย Pseudo เขียนแทนด้วย
li $s0, Naddi $s0,$zero,Nblt $a,$b,Lslt $t0,$a,$b ; bne $t0,$zero,L
move $s0,$s1add $s0,$s1,$zerobgt $a,$b,Lslt $t0,$b,$a ; bne $t0,$zero,L
la $s0, LBLlui + orible $a,$b,Lslt $t0,$b,$a ; beq $t0,$zero,L
not $s0,$s1nor $s0,$s1,$zerobge $a,$b,Lslt $t0,$a,$b ; beq $t0,$zero,L
สูตรจำ slt slt rd, X, Y อ่านว่า “X น้อยกว่า Y ไหม ” — เงื่อนไข ≤ กับ ≥ ไม่มีคำสั่งตรงๆ ต้อง พลิกเป็นนิเสธ เสมอ เช่น a ≤ b ⇔ ไม่ใช่ (b < a)
หลักคิดรวม ① if / while ให้ตรวจนิเสธของเงื่อนไข แล้ว branch ข้ามออกไป ② array A[k] ของ int ⇒ offset = k × 4 ③ index เป็นตัวแปรต้อง sll $t0,$s2,2 ก่อนบวกเข้ากับ base
ไล่ทีละข้อย่อย
4 (a)
a = A[0] + 32; โค้ด
คำตอบ lw $t0, 0($s3) # $t0 = A[0] · offset = 0*4 = 0
addi $s0, $t0, 32 # a = A[0] + 32
ทำไมต้อง 2 คำสั่ง MIPS เป็น load–store architecture — คำสั่งคำนวณทำงานกับ register เท่านั้น แตะหน่วยความจำโดยตรงไม่ได้ ⇒ ต้อง lw เอาค่าเข้ามาก่อน แล้วค่อยบวก ใช้ $t0 เป็นตัวพักได้ เพราะโจทย์ไม่ได้ห้าม (จะ lw $s0,0($s3) แล้ว addi $s0,$s0,32 ก็ถูกเหมือนกัน)
ถ้าพลิกโจทย์ a = A[2] - b; → lw $t0, 8($s3) ; sub $s0, $t0, $s1a = A[0] + A[1]; → lw $t0,0($s3) ; lw $t1,4($s3) ; add $s0,$t0,$t1ถ้า A เป็น char → offset ไม่ต้องคูณ และใช้ lb แทน lw · ถ้าเป็น double → offset = index × 8
4 (b)
A[1] = a; โค้ด
คำตอบ sw $s0, 4($s3) # offset = 1*4 = 4
จุดที่ต้องระวัง A[1] ไม่ใช่ offset 1 แต่เป็น 1 × 4 = 4 เพราะ int กว้าง 4 ไบต์ และลำดับการเขียนคือ sw ค่าที่จะเก็บ , offset(base ) — เอา $s0 ไปเก็บที่ $s3 + 4
ถ้าพลิกโจทย์ A[3] = a; →
sw $s0, 12($s3)A[i] = a; (index เป็นตัวแปร) → ต้องคูณเอง:
sll $t0, $s2, 2 # $t0 = i*4
add $t0, $t0, $s3 # $t0 = &A[i]
sw $s0, 0($t0)A[i+1] = a; →
addi $t0,$s2,1 ก่อน แล้วค่อย
sll
4 (c)
a = -4; โค้ด
คำตอบ addi $s0, $zero, -4 # ห้ามใช้ li $s0,-4 เพราะเป็น pseudo
ทำไมใช้ addi กับ $zero $zero เป็น register ที่มีค่า 0 ตลอดเวลา ⇒ 0 + (-4) = -4 ได้ค่าคงที่ลง register โดยไม่ต้องใช้ pseudo-instruction immediate เป็น 16 บิตมีเครื่องหมาย ใส่ค่า −32768 ถึง 32767 ได้ ⇒ −4 ใส่ตรงๆ ได้เลย
ถ้าพลิกโจทย์ a = 0; → add $s0, $zero, $zero (หรือ addi $s0,$zero,0)a = 100000; → เกิน 16 บิต ต้องใช้ 2 คำสั่ง: lui $s0, 0x0001 ; ori $s0, $s0, 0x86A0b = a; → add $s1, $s0, $zero (แทน move)
4 (d)
if (a < b) { a = b; } a++; โค้ด
คำตอบ slt $t0, $s0, $s1 # $t0 = (a < b) ? 1 : 0
beq $t0, $zero, ENDIF # ไม่จริง → ข้าม body
add $s0, $s1, $zero # a = b (แทน move)
ENDIF:
addi $s0, $s0, 1 # a++ (อยู่นอก if ทำเสมอ)
วิธีคิดทีละบรรทัด ① เงื่อนไข a < b ตรงกับ slt พอดี ⇒ ได้ 1 เมื่อจริง ② ต้องการ “ข้าม body เมื่อไม่ จริง” ⇒ ใช้ beq $t0,$zero กระโดดไป ENDIF ③ a = b เขียนด้วย add $s0,$s1,$zero เพราะ move เป็น pseudo ④ a++ อยู่นอก if ⇒ ต้องวางหลัง label ENDIF ไม่ใช่ในบล็อก
กับดัก ถ้าเอา addi $s0,$s0,1 ไปไว้ก่อน ENDIF: จะกลายเป็น a++ เฉพาะตอนเงื่อนไขจริง ⇒ ผิดความหมายของ C
ถ้าพลิกโจทย์ if (a <= b) { a = b; } → พลิกเป็นนิเสธ: slt $t0,$s1,$s0 (b < a) ; bne $t0,$zero,ENDIFif(a==b){a=0;}else{a=1;} → bne $s0,$s1,ELSE / add $s0,$zero,$zero / j ENDIF / ELSE: addi $s0,$zero,1 / ENDIF:if (a > b) { b = a; } → slt $t0,$s1,$s0 ; beq $t0,$zero,ENDIF ; add $s1,$s0,$zero
4 (e)
while (a <= b) { a += i; } A[0] = a; โค้ด
คำตอบ LOOP: slt $t0, $s1, $s0 # $t0 = (b < a) = นิเสธของ a<=b
bne $t0, $zero, ENDLOOP # b<a จริง → ออกจากลูป
add $s0, $s0, $s2 # a += i
j LOOP
ENDLOOP:
sw $s0, 0($s3) # A[0] = a
จุดตัดสินของข้อนี้ เงื่อนไขคือ a ≤ b ซึ่ง slt ทำตรงๆ ไม่ได้ ⇒ ต้องพลิกเป็น ออกจากลูปเมื่อ b < a โครง while มาตรฐาน: LOOP: ตรวจเงื่อนไข → j LOOP ท้าย body → ENDLOOP: ล่างสุด คำสั่ง sw อยู่นอก ลูป ตรงกับ C ที่ A[0] = a; อยู่หลังปีกกาปิด
กับดัก ① เขียน slt $t0,$s0,$s1 (a < b) แล้วใช้ beq — จะกลายเป็น while(a < b) ตกกรณี a = b ไป ② ลืม j LOOP ⇒ ลูปทำรอบเดียวจบ ③ เอา sw ไปไว้ในลูป ⇒ เขียนซ้ำทุกรอบ ผลลัพธ์สุดท้ายเท่ากันแต่ผิดความหมาย
ถ้าพลิกโจทย์ do { a += i; } while (a < b); → ไม่ต้องตรวจก่อนเข้า:
LOOP: add $s0,$s0,$s2
slt $t0,$s0,$s1
bne $t0,$zero,LOOPfor(i=0;i<10;i++) a+=A[i]; → init
add $s2,$zero,$zero → ตรวจด้วย
slt → body ใช้
sll/add/lw/add → update
addi $s2,$s2,1 →
j กลับ
a = a*8; →
sll $s0,$s0,3 ·
a = a/4; (a ≥ 0) →
srl $s0,$s0,2
คำถาม 5 — Assembly เป็น Machine Code 10 คะแนน
ข้อ 1–2 เป็น I-type ตรงๆ · ข้อ 3–4 คือหัวใจของข้อนี้ — โปรแกรมชุดเดียวกันแต่วางไว้คนละที่ แล้วถามว่า machine code เปลี่ยนตัวไหนบ้าง ลองพิมพ์คำสั่งลงเครื่องแปลงด้านบนเทียบคำตอบได้
รูปแบบคำสั่งและสูตร — ใช้ตอบทั้ง 4 ข้อย่อย
op31–26
rs25–21
rt20–16
rd15–11
shamt10–6
funct5–0
R-type — add rd, rs, rt · op = 000000 เสมอ แยกชนิดด้วย funct
op31–26
rs25–21
rt20–16
immediate / offset15–0
I-type — lw rt, imm(rs) · addi rt, rs, imm · beq rs, rt, offset
op31–26
target address25–0
J-type — j target · jal target
ลำดับฟิลด์สลับกับที่เขียน add rd , rs, rt แต่ในเครื่องเรียง rs · rt · rd · lw rt , off(rs ) แต่ในเครื่อง rs มาก่อน rt เสมอ (ตัวในวงเล็บขึ้นก่อน!)
สูตรที่ต้องท่อง
Branch offset = ( addr(label) − addr(branch) − 4 ) ÷ 4 → เก็บ 16 บิต signed
Branch ปลายทาง = addr(branch) + 4 + offset × 4
Jump field = addr(target) ÷ 4 → เก็บ 26 บิตล่าง
Jump ปลายทาง = (PC+4)[31:28] ‖ field ‖ 00
2's complement 16 บิต = 0x10000 − |n| ⇒ −1 = 0xFFFF · −4 = 0xFFFC · −5 = 0xFFFB
R-type funct I / J op Register เลข
add0x20 lw100011 $zero0
sub0x22 sw101011 $t0–$t78–15
and / or0x24 / 0x25 addi001000 $s016
slt0x2A beq / bne000100 / 000101 $s117
sll / srl0x00 / 0x02 lui / ori001111 / 001101 $s218
jr0x08 j / jal000010 / 000011 $s521
ท่องแค่นี้พอ $sN = 16 + N · $tN = 8 + N · $ra = 31 · $sp = 29
Lab 04 เครื่องแปลง MIPS ↔ machine code
assembly → hex
hex → assembly
lw $s0, 4($s1)
sw $s0, -4($s1)
beq → 0x00400060
j 0x00400054
add $s2, $s1, $zero
0x3C15A000
0x34100027
0xAEB00000
0x08100014
0x02209020
ไล่ทีละข้อย่อย
5.1
lw $s0, 4($s1) แปลงเป็น hex
กางบิตทีละฟิลด์ 100011op = lw
10001rs = $s1 = 17
10000rt = $s0 = 16
0000 0000 0000 0100offset = 4
รวมเป็น
1000 1110 0011 0000 0000 0000 0000 0100 ⇒ จับกลุ่มละ 4 บิตแปลงเป็น hex ได้
0x8E300004
กับดัก lw $s0, 4($s1) — ตัวในวงเล็บ ($s1) คือ rs ต้องใส่ก่อน ส่วน $s0 คือ rt ใส่ทีหลัง · สลับที่กันเมื่อไรได้ 0x8E2E0004 ผิดทันที
ถ้าพลิกโจทย์ lw $t1, 8($s2) → 100011·10010·01001·0008 = 0x8E490008 เปลี่ยนแค่ offset → เปลี่ยนแค่ 4 หลักหลัง ของ hex เท่านั้น (4 หลักแรกยังเป็น 0x8E30)เปลี่ยนเป็น lb → op เปลี่ยนเป็น 100000 ⇒ 0x8230 0004
5.2
sw $s0, -4($s1) แปลงเป็น hex
กางบิตทีละฟิลด์ 101011op = sw
10001rs = $s1
10000rt = $s0
1111 1111 1111 1100offset = −4
offset ติดลบต้องทำ
2’s complement 16 บิต : 0x10000 − 4 =
0xFFFC สังเกตว่า 4 หลักแรกเปลี่ยนจาก 8E เป็น
AE เพราะ op เปลี่ยนจาก lw เป็น sw เท่านั้น
ตารางค่าติดลบที่ใช้บ่อย −1 = 0xFFFF · −2 = 0xFFFE · −3 = 0xFFFD · −4 = 0xFFFC · −5 = 0xFFFB · −8 = 0xFFF8 · −16 = 0xFFF0 · −32 = 0xFFE0
ถ้าพลิกโจทย์ sw $s0, -8($s1) → 0xAE30FFF8 ให้ hex มาแล้วถอดกลับ → กาง 32 บิต ดู 6 บิตแรก: 101011 = sw ⇒ อ่าน rs, rt แล้วแปลง imm กลับเป็นเลขมีเครื่องหมาย
5.3
โปรแกรม 4 บรรทัด เริ่มที่ 0x00400054 4 คะแนน
คำตอบทั้ง 4 บรรทัด address Assembly machine code 0x00400054 lab0: beq $t0, $s1, lab10x11110002 0x00400058 addi $s1, $s1, 10x22310001 0x0040005C j lab00x08100015 0x00400060 lab1: add $s2, $s1, $zero0x02209020
วิธีทดที่ต้องเขียนลงกระดาษ ① ไล่ address ก่อน — ทุกคำสั่งยาว 4 ไบต์ ⇒ …054, …058, …05C, …060 และ lab1 อยู่ที่ 0x00400060 ② beq: offset = ( 0x400060 − ( 0x400054 + 4 ) ) ÷ 4 = 8 ÷ 4 = 2 000100 · 01000($t0) · 10001($s1) · 0x0002 ⇒ 0x11110002 ③ addi: 001000 · 10001 · 10001 · 0x0001 ⇒ 0x22310001 ④ j: field = 0x00400054 ÷ 4 = 0x0100015 ⇒ 000010 ‖ field ⇒ 0x08100015 ⑤ add: 000000 · 10001($s1) · 00000($zero) · 10010($s2) · 00000 · 100000 ⇒ 0x02209020
กับดัก ① ลืมลบ 4 (PC+4) ตอนคิด branch offset ② ลืมหาร 4 ทั้งใน branch และ jump ③ add $s2,$s1,$zero เรียงในเครื่องเป็น rs=$s1, rt=$zero, rd=$s2 — ไม่ใช่ ตามลำดับที่เขียน
ถ้าพลิกโจทย์ สลับให้ j กระโดดไป lab1 แทน → field = 0x00400060 ÷ 4 = 0x0100018 ⇒ 0x08100018เปลี่ยน beq เป็น bne → op เป็น 000101 ⇒ 0x15110002 ถ้า label อยู่ก่อน branch (ลูปย้อนกลับ) → offset ติดลบ เช่นย้อน 3 คำสั่ง ⇒ −4 = 0xFFFC
5.4
โปรแกรมชุดเดิม แต่เริ่มที่ 0x00408054 4 คะแนน
คำตอบ — เปลี่ยนแค่บรรทัดเดียว Assembly 5.3 · เริ่ม 0x00400054 5.4 · เริ่ม 0x00408054 lab0: beq $t0,$s1,lab10x11110002 0x11110002 addi $s1,$s1,10x22310001 0x22310001 j lab00x08100015 0x08102015 lab1: add $s2,$s1,$zero0x02209020 0x02209020
นี่คือประเด็นที่อาจารย์ตั้งใจวัด beq ไม่เปลี่ยน เพราะเก็บ offset สัมพัทธ์ — ระยะจาก branch ถึง label ยังเท่าเดิม (2 คำสั่ง) ไม่ว่าโปรแกรมจะไปวางที่ไหนj เปลี่ยน เพราะเก็บ address สัมบูรณ์ — 0x00408054 ÷ 4 = 0x0102015 ⇒ 0x08102015addi และ add ไม่เปลี่ยน เพราะไม่เกี่ยวกับ address เลย นี่คือเหตุผลที่โค้ดซึ่งใช้แต่ branch เป็น relocatable — ย้ายไปวางที่ไหนก็ยังทำงานถูก
ถ้าพลิกโจทย์ ย้ายไป 0x10400054 → j: 0x10400054 ÷ 4 = 0x04100015 แต่เก็บได้แค่ 26 บิตล่าง = 0x0100015 ⇒ machine code ยังเป็น 0x08100015 และทำงานถูกเพราะ PC อยู่ region 256 MB เดียวกันถามว่า j ยิงได้ไกลแค่ไหน → 26 บิต × 4 = 2²⁸ = 256 MB ภายใน region เดียวกัน (บิต 31–28 มาจาก PC+4) ⇒ ข้าม region ต้องใช้ jr ที่ไปได้ทั้ง 4 GBถามว่า beq ยิงได้ไกลแค่ไหน → ±2¹⁵ คำสั่ง = −32768 … +32767 คำสั่ง (≈ ±128 KB)ถามว่าทำไมต้องหาร 4 → คำสั่ง MIPS ยาว 4 ไบต์เสมอ address จึงลงท้ายด้วย 00 ตลอด ⇒ ไม่ต้องเก็บ 2 บิตนั้น ประหยัดบิตและยิงได้ไกลขึ้น 4 เท่า
คำถาม 6 — Bootstrapping COE 2019 10 คะแนน
ข้อนี้ยาวแต่เป็นงานเดียวซ้ำๆ — ถอด address map จากรูปที่ 4 หนึ่งครั้ง แล้วอ่าน Boot Mem จากรูปที่ 5 ทีละ 4 ไบต์ ก็ตอบได้ครบทั้ง 8 ข้อย่อย กดปุ่มในแล็บด้านบนเพื่อเดินทีละคำสั่งดูก่อนได้
รูปที่ 4 — โครงสร้างคอมพิวเตอร์ COE 2019 · Gate A1 รับ 20 บิต ออก 1 บิต · Big Endian · Bootstrapping Address = 0x00000000
รูปที่ 5 — ข้อมูลภายใน Boot Mem · อ่านแบบ address 0x00000000 มีข้อมูล 0x3C, address 0x00000001 มีข้อมูล 0x15
ขั้นที่ 1 — ถอด address map ของ COE 2019 จากรูปที่ 4
Gate A1 คิดยังไง
โจทย์ให้ YA1 = Ā₁₂ · Ā₁₃ · … · Ā₃₁ ⇒ เป็น AND ของบิตที่กลับค่าทั้งหมด ⇒ จะเป็น 1 ก็ต่อเมื่อ A[31:12] เป็น 0 ทั้งหมด
นั่นคือครอบคลุม address ตั้งแต่ 0 ถึง 2¹²−1 ⇒ 4 KB แรกเท่านั้น
กล่อง เงื่อนไข CS จากรูป ช่วง address ขนาด
Boot Mem CS1 = CS0 = YA1 (ไม่มี ○ = active-HIGH) ⇒ A[31:12] = 0 0x00000000–0x00000FFF 4 KB A[11-0]
Main Memory CS1 ○← YA1 และ CS0 ○← A[31] ⇒ ต้องเป็น 0 ทั้งคู่ 0x00001000–0x7FFFFFFF ≈2 GB A[30-0]
Dev 0 Decoder เปิดเมื่อ CS1 = CS0 = A[31] = 1 X1 = A[30], X0 = A[29] Y0→Dev0 · Y1→Dev1 · Y2→Dev2 · Y3→Dev3 0x80000000–0x9FFFFFFF 512 MB ต่อตัว A[28-0]
Dev 1 0xA0000000–0xBFFFFFFF
Dev 2 0xC0000000–0xDFFFFFFF
Dev 3 0xE0000000–0xFFFFFFFF
ทริกจำ 5 วินาที 0x0–0xFFF = Boot (4 KB) หลักแรก 0-7 ที่เหลือ = Main 8,9 = Dev0 A,B = Dev1 C,D = Dev2 E,F = Dev3
อย่าจำสลับกับรูปที่ 3 รูปที่ 3 ใช้ 0-7 Main · 8-B DMA · C Boot · D Dev0 · E Dev1 · F Dev2 — คนละวงจร คนละ map สิ่งที่ควรท่องคือ วิธีถอด ไม่ใช่ตัวช่วง
ขั้นที่ 2 — อ่าน Boot Mem ทีละ 4 ไบต์ (Big Endian)
Big Endian ไบต์ที่ address ต่ำสุด คือไบต์ที่ มีนัยสำคัญสูงสุด (ซ้ายสุดของ word) ⇒ ไบต์ 3C 15 A0 00 ที่ 0x0–0x3 รวมเป็น word 0x3C15A000 ถ้าเป็น Little Endian จะกลายเป็น 0x00A0153C แล้วคำสั่งเปลี่ยนไปทั้งหมด
Address 4 ไบต์ Machine code op 6 บิตแรก MIPS Assembly
0x00000000 3C 15 A0 00 0x3C15A000 001111 = lui lui $s5, 0xA000
0x00000004 34 10 00 27 0x34100027 001101 = ori ori $s0, $zero, 0x0027
0x00000008 AE B0 00 00 0xAEB00000 101011 = sw sw $s0, 0($s5)
0x0000000C 8E B1 00 04 0x8EB10004 100011 = lw lw $s1, 4($s5)
0x00000010 08 10 00 14 0x08100014 000010 = j j 0x00400050
Lab 08 หัววัด address — วงจรรูปที่ 4 COE 2019 (ของคำถามนี้)
0x00000000
0x00000FFF
0x00001000
0x00400050
0xA0000000
0xC0000000
แผนที่นี้เป็นของ วงจรรูปที่ 4 (COE 2019) เท่านั้น — สังเกตว่า Boot Mem กินแค่ 4 KB แรก ต่างจากวงจรรูปที่ 3 อย่างสิ้นเชิง
Lab 05 เดินโปรแกรม boot ทีละคำสั่ง
ทำคำสั่งถัดไป →
เริ่มใหม่
PC เริ่มที่ Bootstrapping Address = 0x00000000 · Big Endian
Address 4 ไบต์ Machine code MIPS Assembly ผล
ไล่ทีละข้อย่อย
6.1
คำสั่งแรกหลังเปิดเครื่อง 3 ช่องย่อย
คำตอบ Address: 0x00000000 Machine code: 0x3C15A000 MIPS Assembly: lui $s5, 0xA000
วิธีคิด โจทย์บอกว่า Bootstrapping Address = 0x00000000 ⇒ PC เริ่มที่นั่น และ address นี้ตกใน Boot Mem (4 KB แรก) ซึ่งเป็น non-volatile จึงมีคำสั่งค้างอยู่จริงตอนเปิดเครื่อง อ่าน 4 ไบต์แรกจากรูปที่ 5 แบบ Big Endian: 3C 15 A0 00 ⇒ 0x3C15A000 กางบิต: 001111 (op = 0x0F = lui) · 00000 · 10101 (rt = 21 = $s5) · 0xA000ผลลัพธ์: $s5 = 0xA0000000 เพราะ lui ยกค่า 16 บิตขึ้นไปไว้ครึ่งบน ครึ่งล่างเป็น 0
ถ้าพลิกโจทย์ เปลี่ยน Bootstrapping Address เป็น 0x00000010 → คำสั่งแรกกลายเป็น j 0x00400050 ทันทีเปลี่ยนเป็น Little Endian → 4 ไบต์แรกกลายเป็น 0x00A0153C ⇒ op = 000000 = R-type ⇒ ต้องถอดใหม่หมดถามว่าทำไม Boot Mem ต้อง non-volatile → ตอนเปิดเครื่อง RAM ยังว่างเปล่า ต้องมีโปรแกรมค้างอยู่ในชิปจริงถึงจะเริ่มระบบได้
6.2
คำสั่งที่สอง 3 ช่องย่อย
คำตอบ Address: 0x00000004 Machine code: 0x34100027 MIPS Assembly: ori $s0, $zero, 0x0027
วิธีคิด คำสั่งถัดไปอยู่ที่ PC + 4 = 0x00000004 · อ่าน 34 10 00 27 ⇒ 0x34100027 กางบิต: 001101 (op = 0x0D = ori) · 00000 (rs = $zero) · 10000 (rt = 16 = $s0) · 0x0027ผลลัพธ์: $s0 = 0 OR 0x27 = 0x00000027 ⇒ ใช้ ori กับ $zero เพื่อยัดค่าคงที่ลง register
ถ้าพลิกโจทย์ ถามว่าทำไมใช้ ori ไม่ใช่ addi → ได้ผลเหมือนกันในกรณีนี้ แต่ ori ใช้ zero-extend ส่วน addi ใช้ sign-extend ⇒ ถ้าค่าเกิน 0x7FFF ผลจะต่างกันเปลี่ยนค่าเป็น 0x0041 → $s0 = 0x00000041 และคำสั่งกลายเป็น 0x34100041
6.3
คำสั่งที่สาม 3 ช่องย่อย
คำตอบ Address: 0x00000008 Machine code: 0xAEB00000 MIPS Assembly: sw $s0, 0($s5)
วิธีคิดและผลที่เกิดขึ้น อ่าน AE B0 00 00 ⇒ 0xAEB00000 · กางบิต: 101011 (op = 0x2B = sw) · 10101 (rs = $s5) · 10000 (rt = $s0) · offset 0ปลายทางจริง = $s5 + 0 = 0xA0000000 ⇒ hex หลักแรกเป็น A ⇒ ตกในช่วง Dev 1 ⇒ คำสั่งนี้คือการ ส่งค่า 0x27 ออกไปให้อุปกรณ์ Dev 1 ผ่าน memory-mapped I/O — จำไว้ใช้ตอบข้อ 6.8
ถ้าพลิกโจทย์ ถ้าคำสั่งแรกเป็น lui $s5, 0xC000 → ปลายทาง 0xC0000000 ⇒ หลักแรก C ⇒ Dev 2 ถ้าเป็น 0x8000 → Dev 0 · ถ้าเป็น 0x0040 → 0x00400000 ⇒ Main Memory (ไม่ใช่ I/O แล้ว)ถามว่า memory-mapped I/O คืออะไร → อุปกรณ์ถูกจัดสรร address ปนอยู่กับหน่วยความจำ ⇒ ติดต่อด้วย lw/sw ธรรมดา ไม่ต้องมีคำสั่งพิเศษ
6.4
คำสั่งที่สี่ 3 ช่องย่อย
คำตอบ Address: 0x0000000C Machine code: 0x8EB10004 MIPS Assembly: lw $s1, 4($s5)
วิธีคิด อ่าน 8E B1 00 04 ⇒ 0x8EB10004 · 100011 (op = lw) · 10101 (rs = $s5) · 10001 (rt = 17 = $s1) · offset 4ต้นทางจริง = 0xA0000000 + 4 = 0xA0000004 ⇒ Dev 1 อีกครั้ง ⇒ อ่านค่าตอบกลับจากอุปกรณ์เข้ามาเก็บใน $s1
จุดที่มักโดนถามต่อ ถามว่า $s1 มีค่าเท่าไร → บอกไม่ได้ เพราะเป็นค่าที่อ่านมาจาก Dev 1 ซึ่งโจทย์ไม่ได้ให้ — เหมือนกับข้อ 1.5 ที่ค่าบน data bus ระบุไม่ได้
ถ้าพลิกโจทย์ เปลี่ยน offset เป็น 8 → อ่านจาก 0xA0000008 ยังเป็น Dev 1 อยู่ดี (ช่วงของ Dev 1 กว้าง 512 MB)เปลี่ยน rt เป็น $s0 → machine code เป็น 0x8EB00004 และ $s0 จะถูกทับ ⇒ คำตอบข้อ 6.6 เปลี่ยนเป็น “บอกไม่ได้” ทันที
6.5
คำสั่งที่ห้า 3 ช่องย่อย
คำตอบ Address: 0x00000010 Machine code: 0x08100014 MIPS Assembly: j 0x00400050
วิธีคำนวณปลายทางของ j อ่าน 08 10 00 14 ⇒ 0x08100014 · 000010 = op ของ j field 26 บิต = 0 0001 0000 0000 0000 0001 0100 = 0x0100014 ปลายทาง = (PC+4)[31:28] ‖ field ‖ 00 = 0x0 ‖ (0x0100014 × 4) = 0x00400050 address นี้หลักแรกเป็น 0 และไม่อยู่ใน 4 KB แรก ⇒ ตกใน Main Memory — คือกระโดดไปรันโปรแกรมหลัก
ถ้าพลิกโจทย์ ถ้าไม่มีคำสั่ง j นี้ → คำสั่งที่ 6 จะอยู่ที่ 0x14 = 27 A5 00 04 = 0x27A50004 ⇒ addiu $a1, $sp, 4 · คำสั่งที่ 7 ที่ 0x18 = 0x8FA40000 ⇒ lw $a0, 0($sp)ถามว่า j ข้าม region ได้ไหม → ไม่ได้ เพราะบิต 31–28 ยึดมาจาก PC+4 ⇒ ยิงได้แค่ 256 MB ใน region เดียวกัน ต้องใช้ jr ถ้าจะข้าม
6.6
ค่าของ $s0 หลัง 5 คำสั่งแรก 1 ช่อง
คำตอบ $s0 = 0x00000027
ไล่ทีละคำสั่งว่าใครแตะ $s0 บ้าง ① lui $s5, 0xA000 → เขียนลง $s5 ② ori $s0, $zero, 0x27 → เขียนลง $s0 = 0x27 ✓ ③ sw $s0, 0($s5) → อ่าน $s0 ไปเขียนหน่วยความจำ ไม่ได้เปลี่ยนค่า ④ lw $s1, 4($s5) → เขียนลง $s1 ⑤ j → ไม่แตะ register ใดเลย ⇒ มีคำสั่งเดียวที่เขียนลง $s0 ⇒ ค่ายังเป็น 0x00000027
ถ้าพลิกโจทย์ ถามค่า $s5 → 0xA0000000 ถามค่า $s1 → บอกไม่ได้ เพราะอ่านมาจาก Dev 1ถ้าคำสั่งที่ 4 เป็น lw $s0, 4($s5) → $s0 ถูกทับด้วยค่าจากอุปกรณ์ ⇒ ตอบว่า บอกไม่ได้
6.7
คำสั่งที่หกอยู่ที่ address ใด 1 ช่อง
คำตอบ 0x00400050
ทำไมไม่ใช่ 0x00000014 เพราะคำสั่งที่ 5 คือ j 0x00400050 ซึ่ง เปลี่ยน PC ⇒ คำสั่งถัดไปไม่ได้อยู่ที่ 0x10 + 4 = 0x14 แต่ไปที่ปลายทางของ jump แทนกฎ: PC ← PC + 4 ทุกคำสั่ง ยกเว้น branch/jump ที่เป็นจริง
ถ้าพลิกโจทย์ ถ้าคำสั่งที่ 5 เป็น beq ที่เงื่อนไขไม่จริง → PC ไปที่ 0x00000014 ตามปกติถ้าเป็น jal แทน j → ไปที่ 0x00400050 เหมือนกัน แต่ $ra จะถูกตั้งเป็น 0x00000014 ไว้กลับ
6.8
คำสั่งไหนติดต่อ I/O และกับอุปกรณ์ใด เขียนอธิบาย
คำตอบ 0x00000008 → Dev 1 (คำสั่ง sw $s0, 0($s5) เขียนไปที่ 0xA0000000)0x0000000C → Dev 1 (คำสั่ง lw $s1, 4($s5) อ่านจาก 0xA0000004)
วิธีหาแบบเป็นระบบ ① หา ทุกคำสั่ง lw และ sw ในโปรแกรม ⇒ มี 2 ตัวคือที่ 0x08 กับ 0x0C ② คำนวณ address จริง = ค่าใน base register + offset ⇒ 0xA0000000 และ 0xA0000004 ③ เอาไป เทียบ address map ⇒ hex หลักแรกเป็น A ⇒ ตกช่วง Dev 1 (0xA0000000–0xBFFFFFFF)
กับดักของข้อนี้ คำสั่งอีก 3 ตัวไม่นับ — lui, ori, j เป็นการคำนวณ/กระโดดล้วนๆ ไม่แตะ data bus ของอุปกรณ์เลยการ fetch คำสั่งเองไม่นับเป็น I/O — ถึงแม้ทุกคำสั่งจะถูกอ่านมาจาก Boot Mem ก็ตาม เพราะโจทย์ถามถึงการ “ติดต่อ I/O” ผ่าน memory-mapped I/O
ถ้าพลิกโจทย์ ถามว่าคำสั่งไหนติดต่อ Main Memory → ไม่มีเลย ใน 5 คำสั่งแรกถ้า lui ใส่ 0xC000 → ทั้งสองคำสั่งไปคุยกับ Dev 2 แทนถามว่าโปรแกรม boot ชุดนี้ทำอะไร → ตั้ง base address ของ Dev 1 → ส่งค่า 0x27 ไปให้อุปกรณ์ → อ่านค่าตอบกลับ → กระโดดไปรันโปรแกรมหลักที่ 0x00400050 ใน Main Memoryเปลี่ยน Gate A1 เป็นรับ A[31-16] → Boot Mem โตเป็น 64 KB และ Main Memory เริ่มที่ 0x00010000