Topic 12: Single-Cycle Datapath & Control
310-2202 โครงสร้างระบบคอมพิวเตอร์ — สรุปเนื้อหา Topic 12
ภาพรวม Topic 12 — Single-Cycle Datapath & Control
ครั้งนี้เราสร้าง "ฮาร์ดแวร์จริง" ที่รันคำสั่ง RV32I — เปลี่ยนวงรอบ FETCH-DECODE-EXECUTE เป็นบล็อกที่ต่อสายกัน (PC, instruction memory, register file, ALU, immediate generator, data memory, mux, adder) สร้าง datapath ทีละกลุ่มคำสั่ง พร้อมหน่วยควบคุมที่ถอด opcode เป็นสัญญาณ และเข้าใจว่าทำไม single-cycle ถูกต้องแต่ช้า
เมื่อจบครั้งนี้ ผู้เรียนจะสามารถ: ระบุบล็อกของ single-cycle datapath และเส้นทางข้อมูลของแต่ละชนิดคำสั่ง (R/lw/sw/beq/jal) · อธิบายสัญญาณควบคุม (RegWrite/ALUSrc/MemRead/MemWrite/Branch/ALUOp/PCSrc) · อธิบาย critical path และเหตุผลที่ single-cycle ช้า (จูงใจสู่ pipelining)
| หัวข้อ | เนื้อหา |
|---|---|
| 12.1 | เปลี่ยนวงรอบ FETCH-DECODE-EXECUTE เป็นบล็อกฮาร์ดแวร์ 8 ชนิด แล้วต่อเป็น datapath ภาพรวม |
| 12.2 | สร้าง datapath ทีละกลุ่มคำสั่ง R-type → lw/sw → beq → jal/jalr เห็นว่า mux แต่ละตัวเกิดตรงไหนเพราะอะไร |
| 12.3 | หน่วยควบคุม: opcode → สัญญาณ 8 ตัว แล้ว ALU control ถอด funct เป็น operation |
| 12.4 | ทำไม single-cycle ถูกต้องแต่ช้า: critical path ของ lw กำหนด clock period และมองผ่าน Iron Law |
12.1 จากวงรอบคำสั่งสู่บล็อกฮาร์ดแวร์
วงรอบ FETCH-DECODE-EXECUTE คือบล็อกฮาร์ดแวร์จริง
ก่อนหน้านี้เราเห็นวงรอบคำสั่ง (instruction cycle) เป็นขั้นตอนที่ CPU ทำซ้ำ: FETCH → DECODE → EXECUTE ใน Topic นี้เราจะเปลี่ยนแต่ละขั้นให้เป็นบล็อกฮาร์ดแวร์จริง แล้วต่อสายเข้าด้วยกันเป็นทางเดินข้อมูล (datapath) — ขั้นตอนเหล่านี้ไม่ต้องรอขอบนาฬิกาคั่นระหว่างขั้น ฮาร์ดแวร์วางบล็อกเรียงต่อกัน แล้วสัญญาณไหลผ่านทุกบล็อกตามลำดับให้จบภายในหนึ่งคาบเดียว
FETCH (ดึงคำสั่ง): PC (program counter) เก็บที่อยู่คำสั่งปัจจุบัน → ป้อนให้ instruction memory อ่านคำสั่ง 32 บิตออกมา พร้อมกันมี adder บวก PC+4 เตรียมที่อยู่คำสั่งถัดไป (RV32I คำสั่งยาว 4 ไบต์เสมอ) · DECODE (ถอดรหัส): แยกฟิลด์ออกจากคำสั่ง 32 บิต — หมายเลข rs1, rs2, rd ส่งเข้า register file เพื่ออ่านค่า ส่วนบิต immediate ส่งเข้า immediate generator เพื่อขยายเป็นค่า 32 บิต ฟิลด์ opcode/funct ส่งให้หน่วยควบคุม · EXECUTE (ปฏิบัติการ): ALU คำนวณ (บวก/ลบ/AND/เปรียบเทียบ) แล้วผลลัพธ์อาจใช้เป็นที่อยู่เข้า data memory (สำหรับ load/store) หรือเขียนกลับเข้า register file
บล็อกพื้นฐาน 8 ชนิดของ single-cycle datapath
| บล็อก | หน้าที่ | ชนิด |
|---|---|---|
| PC | เก็บที่อยู่คำสั่งปัจจุบัน อัปเดตทุกขอบนาฬิกา | state (มี clock) |
| Instruction memory | รับที่อยู่ → คืนคำสั่ง 32 บิต (อ่านอย่างเดียว) | combinational (อ่าน) |
| Register file | อ่าน 2 พอร์ต (rs1, rs2) พร้อมกัน, เขียน 1 พอร์ต (rd) | state + อ่าน combinational |
| ALU | คำนวณเลขคณิต/ตรรกะ ตาม ALU control | combinational |
| Immediate generator | ดึง imm จากคำสั่ง แล้ว sign-extend เป็น 32 บิต | combinational |
| Data memory | load (อ่าน) / store (เขียน) ข้อมูลในหน่วยความจำ | state + อ่าน combinational |
| Mux | เลือกหนึ่งจากหลายเส้นทางข้อมูลตามสัญญาณควบคุม | combinational |
| Adder | บวกที่อยู่ (PC+4, PC+imm สำหรับ branch) | combinational |
บล็อกทั้งแปดต่อกันเป็นวง: PC ป้อนที่อยู่ให้ Instruction memory คำสั่งที่อ่านได้ส่งเลขรีจิสเตอร์ให้ Registers ค่าที่อ่านได้เข้า ALU และผลของ ALU กลายเป็น Address ของ Data memory แล้ววนกลับมาที่ PC ทางตัวบวก — ที่มา: Patterson & Hennessy, Computer Organization and Design (RISC-V Edition) รูปที่ 4.1
ต่อบล็อกเป็น datapath ภาพรวม
ลำดับการไหลในหนึ่งคาบ: PC ชี้ที่อยู่ → instruction memory คืนคำสั่ง 32 บิต → คำสั่งถูกแยกฟิลด์ (rs1,rs2 → register file; imm → immediate generator) → mux หน้า input ที่สองของ ALU เลือกว่าจะใช้ค่าจาก rs2 หรือค่า immediate → ALU คำนวณ → ผลลัพธ์เป็นค่าที่จะเขียนกลับ register file หรือที่อยู่เข้า data memory → mux หน้าพอร์ตเขียนของ register file เลือกจะเขียนผล ALU หรือข้อมูลจาก data memory (load) หรือค่า PC+4 (jal/jalr) → adder บวก PC+4 และอีก adder บวก PC+imm สำหรับ branch/jump → mux หน้า PC เลือกหนึ่งในนั้น แล้วล็อกเข้า PC ที่ขอบนาฬิกาถัดไป (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.2)
register file: ทำไม 2 พอร์ตอ่าน 1 พอร์ตเขียน
คำสั่งแบบ R ของ RV32I (เช่น add x1,x2,x3) ต้องอ่านรีจิสเตอร์ต้นทางสองตัวพร้อมกัน (rs1, rs2) ในคาบเดียว จึงต้องมี 2 พอร์ตอ่าน ที่ทำงานขนานกัน และคำสั่งหนึ่งเขียนผลกลับได้มากสุดหนึ่งปลายทาง (rd) จึงมี 1 พอร์ตเขียน การเขียนเกิดที่ขอบนาฬิกา ส่วนการอ่านคืนค่าทันที
mux และ adder: กาวที่เชื่อมบล็อก
mux สำคัญมีสามจุด (สัญญาณสั่งมาจากหน่วยควบคุม 12.3):
| mux อยู่ที่ | เลือกระหว่าง | สัญญาณสั่ง |
|---|---|---|
| ขา B ของ ALU | rs2 / immediate | ALUSrc |
| write-back (หน้าพอร์ตเขียน rd) | ผล ALU / ค่า load / PC+4 | MemToReg |
| หน้า PC | PC+4 / PC+imm / rs1+imm (jalr) | PCSrc |
กำหนด x2 = 100, หน่วยความจำที่ที่อยู่ 148 เก็บค่า 777, รหัสเครื่อง 0x03012283, PC ปัจจุบัน = 200
- FETCH: PC=200 → instruction memory คืน 0x03012283 = lw x5,48(x2) · adder(+4) คำนวณ 200+4=204
- DECODE: rs1=x2, rd=x5, imm=48 (I-format, sign-extend) · register file อ่าน x2=100 · immediate generator ขยาย imm→48
- mux ขา B: ALUSrc เลือก imm (ไม่ใช่ rs2) → ป้อน 48 เข้า ALU input ที่สอง
- EXECUTE/ALU: บวก 100+48=148 ← ที่อยู่ของหน่วยความจำที่จะโหลด
- MEM: MemRead=1 → อ่าน data memory ที่ address 148 → ได้ค่า 777
- WRITE-BACK: write-back mux เลือก "ค่าจาก data memory" (ไม่ใช่ผล ALU) → 777, RegWrite=1 → เขียน 777 ลง x5
- เลือก PC ถัดไป: lw ไม่ใช่ branch/jump → PC mux เลือก PC+4=204
- D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4
12.2 สร้าง Datapath ทีละกลุ่มคำสั่ง
วิธีสร้างแบบ COD: ค่อย ๆ เพิ่มทีละชนิดคำสั่ง
วิธีสร้าง datapath แบบ COD (Patterson & Hennessy) คือค่อย ๆ เพิ่มทีละชนิดคำสั่ง: เริ่มจากชิ้นส่วนที่ทุกคำสั่งใช้ร่วมกัน (instruction fetch) แล้วถามทีละกลุ่มว่า "คำสั่งนี้ต้องการ wire/mux/หน่วยใหม่อะไรเพิ่ม" ทำให้เห็นว่า mux และสัญญาณควบคุมแต่ละตัวมีไว้เพื่ออะไร แทนที่จะจ้องวงจรสำเร็จทั้งก้อน
ชิ้นส่วนร่วม (instruction fetch)
ทุกคำสั่งเริ่มเหมือนกัน: ใช้ PC ชี้ instruction memory เพื่ออ่านคำสั่ง 32 บิต แล้วเตรียม PC ของรอบถัดไปด้วย PC+4 (คำสั่งยาว 4 byte เสมอใน RV32I — ต่างจาก LC-3 ที่นับที่อยู่เป็น word จึงบวก +1)
ขั้นที่ 1: R-type (add, sub, and, or, slt)
รูปแบบ: rd = rs1 (op) rs2 ต้องการ register file (อ่าน 2 พอร์ต rs1,rs2 พร้อมกัน เขียน 1 พอร์ตที่ rd) ต่อเข้า ALU ซึ่งทำ op ตาม funct3/funct7 แล้วเขียนผลกลับ register file สิ่งที่เพิ่ม: register file + ALU + สาย rs1/rs2 → ALU → rd และสัญญาณ RegWrite (อนุญาตเขียน rd) กับ ALUControl (เลือกว่า ALU ทำ add/sub/and/…) — ยังไม่มี mux ที่ไหนเลยเพราะทุกขามีแหล่งข้อมูลทางเดียว
ซ้ายคือ register file: ช่อง Read register 1, Read register 2 และ Write register กว้าง 5 บิต ให้ Read data 1 กับ Read data 2 ออกมาสองค่าพร้อมกัน ส่วน RegWrite คุมพอร์ตเขียนอย่างเดียว ขวาคือ ALU ที่รับ ALU operation 4 บิตแล้วให้ ALU result กับธง Zero — ยังไม่มี mux เลยเพราะทุกขามีแหล่งข้อมูลทางเดียว (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.7)
x6=7, x7=32, รหัสเครื่อง 0x007302B3
- FETCH: PC → instruction memory คืน 0x007302B3 · ตัวบวก+4 เตรียม PC+4 ขนานกัน
- แยกฟิลด์: rs1=6, rs2=7, rd=5, funct3=000, funct7=0000000
- register file อ่าน x6=7, x7=32
- ALU: ALUControl สั่ง add → 7+32=39
- เขียนกลับ: RegWrite=1 → x5←39 ที่ขอบนาฬิกา, PC←PC+4
ขั้นที่ 2: เพิ่ม lw / sw
load/store เป็นรูปแบบ I และ S: ที่อยู่ = rs1 + immediate เกิดความต้องการใหม่ 3 อย่าง: ImmGen (ดึง immediate จากคำสั่งแล้ว sign-extend เป็น 32 บิต — lw ดึงแบบ I-type, sw ดึงแบบ S-type) · ALU ต้องรับ immediate เป็นตัวตั้งที่ 2 แทน rs2 → เพิ่ม ALUSrc mux ที่ขาเข้าที่สองของ ALU (R-type เลือก rs2 ส่วน lw/sw เลือก immediate) · Data Memory — lw อ่าน (MemRead) เอาค่ากลับมาเขียน rd, sw เขียน (MemWrite) ค่าจาก rs2 ลงหน่วยความจำ (sw ไม่เขียน rd) และเพราะค่าที่จะเขียนกลับ rd มาได้จาก 2 ทาง (ผล ALU สำหรับ R-type, ค่าจาก memory สำหรับ lw) จึงต้องเพิ่ม MemtoReg mux หน้าพอร์ตเขียนของ register file
| สิ่งที่เพิ่ม | ทำไม | สัญญาณ |
|---|---|---|
| ImmGen | lw/sw ต้องมี imm 32 บิต | — |
| ALUSrc mux | ขา B ของ ALU มีสองแหล่ง (rs2 / imm) | ALUSrc |
| Data Memory | lw อ่าน / sw เขียน | MemRead, MemWrite |
| MemtoReg mux | พอร์ตเขียน rd มีสองแหล่ง (ALU / memory) | MemToReg |
โครง R-type เดิมไม่ถูกรื้อ เพียงแทรก mux เข้าไป — นับกล่อง Mux ได้สองกล่อง: ALUSrc (ขา 0=Read data 2, ขา 1=ค่าจาก Imm Gen) และ MemtoReg (ขา 1=Read data จาก Data memory, ขา 0=ALU result) (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.10)
ขั้นที่ 3: เพิ่ม beq (branch)
beq rs1, rs2, label: ถ้า rs1==rs2 ให้กระโดดไปที่ PC+offset (PC-relative, offset เป็น signed 13 บิต ทวีคูณของ 2) ถ้าไม่เท่ากันไป PC+4 ตามปกติ ความต้องการใหม่: เปรียบเทียบ rs1 กับ rs2 — ตั้ง ALU ทำ subtract แล้วดูธง Zero (Zero=1 เมื่อ rs1−rs2=0) ใช้ ALU ตัวเดิม ไม่ต้องเพิ่มตัวเปรียบเทียบ (branch ป้อน rs2 เข้า ALU ดังนั้น ALUSrc=0) · Branch adder (ตัวบวกแยก) คำนวณ PC+immediate ขนานไปกับ PC+4 · PCSrc mux ที่ทางเข้า PC เลือกระหว่าง PC+4 กับ PC+offset สัญญาณเลือกคือ PCSrc = Branch AND Zero — กระโดดเฉพาะเมื่อเป็นคำสั่ง branch และเงื่อนไขเป็นจริง
ImmGen ที่มีอยู่ถูกใช้ซ้ำ เพียงเพิ่มรูปแบบ B-type — beq จึงไม่เพิ่ม ALU หรือพอร์ตรีจิสเตอร์ใหม่เลย ของใหม่จริงมีแค่ branch adder, PCSrc mux และเกต AND ของ Branch กับ Zero (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.9, 4.25)
- x5=7, x6=7: register file ส่ง rs1=7, rs2=7
- ALU ทำ subtract → 7−7=0 → Zero=1
- control ถอด opcode 1100011 (beq) → Branch=1
- PCSrc = Branch AND Zero = 1 AND 1 = 1 → เลือกขากระโดด
- branch adder: PC+imm = 0x40+16 = 0x50 → PC ถัดไป = 0x50
ขั้นที่ 4: เพิ่ม jal และ jalr (jump)
สองคำสั่งนี้ "กระโดดเสมอ" (unconditional) และต้องเก็บ return address = PC+4 ลง rd
| คำสั่ง | ที่อยู่เป้าหมาย | ใครคำนวณ | ค่าที่เขียนลง rd |
|---|---|---|---|
| jal rd, label (J-type) | PC + imm (PC-relative) | branch adder ตัวเดิม | PC + 4 |
| jalr rd, rs1, imm (I-type) | rs1 + imm (บิตล่างสุดบังคับเป็น 0) | ALU (ALUSrc เลือก immediate) | PC + 4 |
ผลต่อ datapath มีสองจุด: ทางเข้า PC มีสามแหล่ง (PC+4, PC+imm, ผล ALU) — PCSrc mux ขยายเป็นหลายขา และสาย Jump=1 บังคับเลือกขากระโดดโดยไม่ดู Zero · พอร์ตเขียน rd มีแหล่งที่สาม คือ PC+4 — write-back mux ขยายอีกหนึ่งขา
ของใหม่ในผังนี้มีแค่สองขา — ขา PC+4 ที่เข้า write-back mux (สำหรับเก็บ return address) และขาผล ALU ที่เข้า PCSrc mux (สำหรับ jalr) ที่เหลือเป็นบล็อกเดิมทั้งหมด
- jal x1,L (PC=0x40, imm=+8): ตัวบวก+4 = 0x44 (return address) · branch adder PC+imm=0x48 (เป้าหมาย) · Jump=1 → PCSrc mux เลือก PC+imm → PC ถัดไป=0x48 · RegWrite=1, write-back mux เลือกขา PC+4 → x1←0x44
- jalr x0,0(x1) (x1=0x44,imm=0): ALUSrc=1: ALU บวก rs1+imm=0x44+0=0x44 · Jump=1 → PCSrc mux เลือกขา "ผล ALU" → PC ถัดไป=0x44 · rd=x0 → เขียน PC+4 ลง x0 ไม่มีผล — นี่คือ ret นั่นเอง
สรุปภาพรวม: แต่ละชนิดเพิ่มอะไร
| ชนิดคำสั่ง | หน่วย/สายใหม่ | mux ที่เพิ่ม | control ใหม่ |
|---|---|---|---|
| R-type | register file, ALU | — | RegWrite, ALUControl |
| lw / sw | ImmGen, data memory | ALUSrc, MemtoReg | MemRead, MemWrite |
| beq | branch adder (PC+imm) | PCSrc (หน้า PC) | Branch (+ ลอจิก AND กับ Zero) |
| jal / jalr | (ใช้ adder/ALU เดิม) | ขยาย PCSrc + write-back mux (เพิ่มขา PC+4) | Jump, jalr-select |
- D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4
12.3 หน่วยควบคุม (Control Unit)
ถอด opcode เป็นสัญญาณควบคุม
datapath ใน 12.2 มี mux, หน่วยความจำ และ register file ที่ทำได้ทุกอย่าง — แต่จะทำอะไรขึ้นกับชนิดของคำสั่ง หน่วยควบคุม (control unit) คือวงจรเชิงรวม (combinational) ที่รับ opcode (7 บิต) แล้วผลิตสัญญาณควบคุม (control signal) ไปสั่งทุก mux และทุก enable ให้ตรงกับคำสั่งนั้น
| สัญญาณ | ค่า 0 หมายถึง | ค่า 1 หมายถึง | คำสั่งที่ตั้ง 1 |
|---|---|---|---|
| RegWrite | ไม่เขียน register file | เขียนค่ากลับที่ rd ปลายคาบ | R-type, addi, lw, jal/jalr |
| ALUSrc | ขา B ของ ALU = rs2 | ขา B ของ ALU = immediate | addi, lw, sw, jalr |
| MemRead | ไม่อ่าน data memory | อ่าน data memory ตามที่อยู่จาก ALU | lw |
| MemWrite | ไม่เขียน data memory | เขียนค่า rs2 ลง data memory | sw |
| MemToReg | เขียนกลับผล ALU | เขียนกลับค่าที่อ่านจาก memory | lw (เป็น X เมื่อ RegWrite=0) |
| Branch | ไม่ใช่ branch | "อาจ" กระโดด — ต้องรวมกับธง Zero ก่อน | beq/bne |
| Jump | ไม่ใช่ jump | กระโดดแน่นอน | jal, jalr |
| ALUOp (2 บิต) | 00=สั่งบวก · 01=สั่งลบ · 10=ให้ ALU control ไปดู funct | lw/sw=00, branch=01, R/I-type=10 | |
PCSrc ไม่ใช่ output ของหน่วยควบคุมโดยตรง แต่คำนวณจาก PCSrc = (Branch AND Zero) OR Jump — ดีไซน์ที่มี jal/jalr ขยาย write-back mux เป็นสามขา บางตำราจึงเรียก MemToReg ว่า WBSel เป็นสัญญาณตัวเดียวกัน
วงรี Control รับแค่ opcode 7 บิต แล้วแตกออกเป็นเจ็ดสาย ปลายสายทุกเส้นไปจบที่ mux หรือพอร์ต enable ส่วนเกต AND ที่รวม Branch กับ Zero เป็นตัวเลือกของ mux หน้า PC คือ PCSrc (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.20, 4.21)
ALU control: decoder สองชั้น
หน่วยควบคุมหลักไม่บอก ALU ตรง ๆ ว่าทำอะไร มันส่งแค่ ALUOp 2 บิตแบบหยาบ วงจรเล็กชื่อ ALU control เป็น decoder ชั้นสอง: รับ ALUOp + funct3 + บิต 30 ของ funct7 แล้วผลิตสัญญาณ ALU operation 4 บิต บิต 30 คือตัวแยก add/sub และ srl/sra
| ALUOp | ชนิดคำสั่ง | ALU ทำอะไร | ดู funct ไหม |
|---|---|---|---|
| 00 | lw, sw | บวก (ฐาน + offset) | ไม่ดู |
| 01 | branch (beq/bne/...) | ลบ (เพื่อหา Zero) | ไม่ดู |
| 10 | R-type / I-type คำนวณ | ตาม funct3 (+funct7 บิต30) | ดู |
ตารางค่าสัญญาณควบคุมต่อชนิดคำสั่ง
| คำสั่ง | RegWrite | ALUSrc | MemRead | MemWrite | MemToReg | Branch | Jump | ALUOp |
|---|---|---|---|---|---|---|---|---|
| R-type | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
| I-type คำนวณ (addi/andi/slti) | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 10 |
| lw | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 00 |
| sw | 0 | 1 | 0 | 1 | X | 0 | 0 | 00 |
| beq / branch | 0 | 0 | 0 | 0 | X | 1 | 0 | 01 |
| jal | 1 | X | 0 | 0 | PC+4 | 0 | 1 | X |
| jalr | 1 | 1 | 0 | 0 | PC+4 | 0 | 1 | 00 |
(X = don't care; ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.22)
สามจุดที่มักสับสน: sw: MemToReg=X เพราะ RegWrite=0 ไม่มีการเขียนกลับ · beq: ALUOp=01 สั่งลบเพื่อหา Zero แล้ว PCSrc=Branch AND Zero ตัดสินจริง · jal: ALUSrc/ALUOp=X เพราะเป้าหมายมาจาก adder (PC+imm) ไม่ใช่ ALU ส่วน jalr ต้องใช้ ALU บวก rs1+imm จึงตั้ง ALUSrc=1, ALUOp=00
- ถอด opcode=0110011 → R-type → เข้าแถว R-type ของตารางค่าสัญญาณ
- อ่านสัญญาณหลัก: RegWrite=1, ALUSrc=0, MemRead=0, MemWrite=0, MemToReg=0, Branch=0, Jump=0, ALUOp=10
- ALU control: ALUOp=10 → ดู funct3=000 (กลุ่ม add/sub) → ดูบิต 30 ต่อ: funct7 บิต 30=1 → เลือก sub (ถ้าเป็น 0 จะเป็น add)
- ALU operation สุดท้าย = SUB (รหัส 4 บิตตามตำรา: AND 0000, OR 0001, add 0010, subtract 0110) → คำนวณ x6−x7 → เขียนกลับ x5
- D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4
12.4 ทำไม Single-Cycle ถูกต้องแต่ช้า
ถูกต้องด้วยความเรียบ แต่ช้าด้วย critical path
ดาต้าพาธแบบ single-cycle ทำทุกคำสั่งให้เสร็จภายในหนึ่งสัญญาณนาฬิกา (one clock cycle): ปลายไซเคิลค่าทั้งหมดถูกเขียนกลับ (register file, PC, memory) แล้วไซเคิลใหม่จึงเริ่ม แบบนี้ถูกต้องเสมอ เพราะไม่มีคำสั่งสองตัวซ้อนทับกันในเวลาเดียว จึงไม่มี hazard ใด ๆ ให้กังวล ราคาที่จ่ายคือความเร็ว: สัญญาณนาฬิกาต้องยาวพอให้คำสั่งที่ช้าที่สุดทำงานครบทุกขั้นภายในไซเคิลเดียว นั่นคือ clock period ต้องครอบ critical path = เส้นทางหน่วงเวลา (propagation delay) ที่ยาวที่สุดในวงจร
คำสั่งที่ยาวที่สุดคือ lw
เส้นทางของ lw คือ PC → IMEM → register read → ALU → DMEM → write-back ต่อกันเป็นอนุกรม คำสั่งอื่นสั้นกว่า เช่นกลุ่ม R (add) ไม่แตะ data memory จึงข้ามขั้น DMEM ไปได้ ส่วน sw ไม่มี write-back เข้า register file แต่ในเครื่อง single-cycle clock period ถูกตั้งตาม lw ที่ยาวสุด
ทุกคำสั่งใช้เวลาเท่ากัน แม้คำสั่งที่สั้น
เพราะ clock period คงที่ตาม critical path ของ lw คำสั่งที่ "ทำงานน้อย" จึงไม่ได้เร็วขึ้น มันแค่นั่งรอจน clock edge ถัดไปมาถึง เวลาที่เกินจากงานจริงคือเวลาว่างเปล่า (slack ที่เสียไป)
กำหนด: IMEM 200 ps, register read 100 ps, ALU 200 ps, DMEM 200 ps, register write 100 ps
| คำสั่ง | IMEM | regread | ALU | DMEM | regwrite | รวม (ps) |
|---|---|---|---|---|---|---|
| R-type (add) | 200 | 100 | 200 | — | 100 | 600 |
| lw | 200 | 100 | 200 | 200 | 100 | 800 |
| sw | 200 | 100 | 200 | 200 | — | 700 |
| beq | 200 | 100 | 200 | — | — | 500 |
CPI = 1 แต่ clock ยาว — มอง Iron Law
เครื่อง single-cycle มี CPI (Cycles Per Instruction) = 1 ซึ่งดูดีในแง่หนึ่ง แต่ราคาคือ clock period ยาวมาก นำมาประกอบกับสมการพื้นฐานของสมรรถนะ (Iron Law of Performance):
นี่คือแรงจูงใจสู่ pipelining (Topic 13): แทนที่จะรอให้ lw วิ่งครบเส้นทางในไซเคิลเดียว เราซอยเส้นทางเป็นช่วง (stage) สั้น ๆ แล้วตั้ง clock period ตามstage ที่ยาวที่สุด (ไม่ใช่ผลรวมทั้งเส้น) clock period สั้นลงมาก แลกกับ CPI ที่สูงกว่า 1 เล็กน้อยจาก hazard — แต่ผลคูณ Iron Law โดยรวมดีขึ้นชัดเจน (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.29)
โปรแกรมมี IC = 1,000,000 คำสั่ง single-cycle: CPI=1, clock period=800 ps เทียบกับ pipeline: clock period=200 ps แต่ CPI จริง=1.25 เพราะ hazard
- single-cycle: 1,000,000 × 1 × 800 ps = 800,000,000 ps = 800 µs
- pipeline: 1,000,000 × 1.25 × 200 ps = 250,000,000 ps = 250 µs
- speedup = 800 µs / 250 µs = 3.2 เท่า
- D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4